mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
- attempt-number-semantics: name the real bucket column
(consecutiveFailuresByKind) and drop a drifted line citation
- backup-restore: show the smoke-test response inside the standard
{ data, error } envelope
- deploy: the manual migration step now uses the bundled Prisma CLI
via node (the image strips pnpm); WHOOP vars are already on the
shipped compose whitelist
- encryption-key-rotation: point at the encrypted-column registry
instead of a five-model list several releases behind
- env-check: the CI gate shipped (env-check.yml); the partial-APNs
example now shows the synthetic all-or-none row and the non-zero exit
- password-reset: the admin API reset route exists; only the
self-service route does not
- tls-cert-pin: stop asserting client-repo pin-set details that cannot
be audited from this repository
- drop v141-followup-issues.md: a v1.4.1 punch list whose items are
fixed, removed, or superseded; keeping it under docs/ops invites
re-fixing shipped work
66 lines
2.6 KiB
Markdown
66 lines
2.6 KiB
Markdown
# Password reset (operator)
|
|
|
|
A self-hosted operator can reset a single user's password from inside the
|
|
running container. This is an **operator-only** maintenance path — there is no
|
|
self-service reset route for users, and it bypasses the normal
|
|
account-recovery flow. Admins do have an in-app alternative:
|
|
`POST /api/admin/users/{id}/reset-password` (cookie-admin only) sets a new
|
|
password and destroys the user's sessions, tokens, and trusted devices. The
|
|
CLI remains the path when no admin can log in. Use it only for an account you
|
|
administer (for example, after a forgotten password on a single-user
|
|
instance).
|
|
|
|
> **Operator-only.** The script writes directly to the `users` table and is
|
|
> intended to be run by the host operator with database access. Treat the new
|
|
> password like any other secret: pass it on stdin (it is read without echoing)
|
|
> rather than as a shell argument where it would land in shell history and the
|
|
> process list.
|
|
|
|
## Run it
|
|
|
|
The reset CLI ships in the image and runs under plain `node` against the built
|
|
runtime (the standalone image strips `tsx`, so this is a `.mjs` that uses the
|
|
in-image `pg` and `@node-rs/argon2` directly and reuses the app's exact Argon2id
|
|
parameters):
|
|
|
|
```sh
|
|
docker compose exec app node scripts/reset-password.mjs <username-or-email>
|
|
```
|
|
|
|
The script prompts for the new password without echoing it, then prints a
|
|
confirmation that names only the user — never the password:
|
|
|
|
```
|
|
New password (input hidden):
|
|
reset-password: password updated for "alice"
|
|
```
|
|
|
|
You can also pass the password as a second argument for non-interactive use,
|
|
but prefer the stdin prompt so the secret stays out of shell history:
|
|
|
|
```sh
|
|
docker compose exec app node scripts/reset-password.mjs alice 'a-strong-passphrase'
|
|
```
|
|
|
|
## Behaviour
|
|
|
|
- The identifier is matched **case-insensitively** against `username` **or**
|
|
`email`, mirroring the login lookup.
|
|
- The match must resolve to exactly one user. Zero matches exits non-zero with
|
|
`no user matches "<id>"`; multiple matches exits non-zero and refuses to guess.
|
|
- The new password must be at least 12 characters (the app's minimum).
|
|
- The password is hashed with Argon2id using the same cost parameters as the
|
|
application (`src/lib/auth/argon2-params.mjs`), so the stored hash is
|
|
identical to one the app would mint.
|
|
- `DATABASE_URL` is read from the environment — the same variable the app uses,
|
|
honouring any `sslmode=` in the connection string.
|
|
|
|
## Source checkout
|
|
|
|
From a source checkout (not the production image) the same script runs under
|
|
Node directly:
|
|
|
|
```sh
|
|
DATABASE_URL='postgres://…' node scripts/reset-password.mjs <username-or-email>
|
|
```
|