Admins can now reset the password of an OAuth-only user, which creates
their credentials and enables password login; the new 'Disable password
login' row action removes those credentials again. Resetting a password
revokes all existing sessions.
- add users.password_login_enabled column (migration 0005)
- admin reset: set password, enable login, revoke sessions
- disable action: clear password, disable login (only on OAuth-linked rows)
- account endpoint: refuse password updates on OAuth-linked users
- e2e: wipe the test DB before the webServer starts (Playwright starts
the server before globalSetup, so wiping there orphaned the DB file)
- row-actions strip test: five square actions, wrap on phone widths
Issue #77 was caused by these docs. FEATURES.md promised "Reset
passwords for any user" when no such control existed, so a user
reasonably filed a bug against a promise the software never made. The
docs were describing the API surface, not the UI. Now that the control
ships, they describe what it actually does — and the same audit turned up
three neighbouring claims that were already wrong.
Corrected:
- "Reset passwords for any user" — it is any user other than yourself,
whose account signs in with a password, and Crumbs generates the
password and shows it once rather than the admin choosing one.
- "Revoke all sessions ... for any user" — Revoke has always been
self-excluded in the UI. Wrong before this feature existed.
- The AUTH.md sentence covering both capabilities in one breath, so
fixing only the reset half would have left the revoke half wrong.
- Two references to a "Settings > Users > Invite User" affordance that
does not exist; the section is headed Create User. Same defect as #77
— documentation promising UI that was never built — one of them buried
in the Authentik setup guide.
Newly documented, all previously silent:
- The sole-admin dead end, and its bound. There is no forgot-password
flow, the reset control excludes your own row, and Profile requires
your current password. Another admin can reset you. Failing that, an
OAuth sign-in recovers the account when a provider is configured and
the email matches — at the cost of handing the account to that
provider permanently. Only with no second admin and no provider is
there genuinely no way back in through the app. An earlier draft
stated that absolutely, which would have pushed an operator toward
restoring from backup when one OAuth sign-in would have done.
- The OAuth pre-link trap: a password set during invite works only until
the first OAuth sign-in, which hands the account to the provider.
Ticking OAuth-only avoids the surprise. Tracked for a real fix in #78.
- That the reset notification is best-effort. Both layers swallow send
errors, so a successful reset is not evidence the user was told.
- That a displayed password is only good until that account's password
changes again, by any route, with nothing marking it stale.
- That an unconfirmed reset shows the password anyway, and that one
should be kept and retried.
- That the control is also unavailable while a password is on screen —
the one unavailable state that shows no reason, so an admin meeting a
dead button has somewhere to look.
No openapi.yaml or API.md change: neither documents /api/admin/* at all,
so the new 400 needs no spec edit and pnpm docs:api regenerates nothing
relevant. That gap predates this work.
Verified: pnpm check 0 errors / 14 pre-existing warnings. Every factual
claim traced to code by the author and again by an independent reviewer,
which is how the false recovery claim was caught.
Reviewed-by: adversarial-review (13 of 14 claims verified true on the
first pass; failed for one false absolute, since bounded)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbqJcFofZSbhbGwUwZP3wU
Create AUTH.md as the canonical reference for authentication: password
auth, OAuth/SSO setup, provider-specific guides (Authentik, Keycloak,
Okta/Auth0), env vars, callback URLs, security notes, and
troubleshooting. Fixes incorrect env var names and callback URL patterns
that had drifted in DEPLOYMENT.md. Other docs now cross-link to AUTH.md
instead of duplicating content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>