fix(server): seed-admin self-heals the admin role + signs a ledger event
Field failure on park-buzi: reset-db --users wipes the roles table and points to seed-admin — which inserted the user with roleId "admin" without recreating the role row (migration 0007 never re-runs), dying on the role_id FOREIGN KEY. The script now upserts the built-in admin role first (the row alone suffices — admin permissions resolve in code). It also appends a SIGNED config_change (admin.passwordReset / admin.seeded, operator console:seed-admin) via the server's compiled EventLog + signer: a console seed/reset by the Linux admin can't be gated by the app, but it stays attributable in the chain. Best-effort — no build/signing key warns loudly and proceeds (locking an admin out to protect an audit line would invert the priority). Both paths verified against a scratch DB reproducing the post-reset state. Runbook: appliance-provisioning §7e — lost app-admin password reset via FORCE=1 (interactive preferred; sessions not revoked → rotate JWT_SECRET if theft suspected); §7d notes the FK failure + self-heal. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
type: reference
|
||||
tags: [parking, deployment, appliance, hardening, runbook, offline-first]
|
||||
sources: []
|
||||
updated: 2026-06-30
|
||||
updated: 2026-07-06
|
||||
status: settled
|
||||
---
|
||||
|
||||
@@ -384,8 +384,41 @@ docker exec -it \
|
||||
> setup only). It is a **training/demo** tool — never run on a production booth's data.
|
||||
|
||||
After `--users`/`--all` (users cleared), re-seed the first admin exactly as in §7b
|
||||
(`docker exec … node scripts/seed-admin.mjs`) so someone can log back in. For dev (where `pnpm` exists)
|
||||
the same script is `pnpm db:reset --financial` — see [[local-dev-workflow]].
|
||||
(`docker exec … node scripts/seed-admin.mjs`) so someone can log back in. Since 2026-07-06 the seed
|
||||
script **self-heals the built-in `admin` role row** that this reset also wipes — before that fix the
|
||||
documented re-seed died on a `role_id` FOREIGN KEY error (field failure on `park-buzi`). For dev
|
||||
(where `pnpm` exists) the same script is `pnpm db:reset --financial` — see [[local-dev-workflow]].
|
||||
|
||||
### 7e. Lost APP admin password — reset from the Linux admin account (2026-07-06)
|
||||
|
||||
The app's admin password lives only as a bcrypt hash in the booth DB; there is no in-app recovery
|
||||
(nobody above the admin exists to send a reset). The recovery path is the **Linux `admin` account**
|
||||
(the only user in the `docker` group): the seed script doubles as the password-reset tool via
|
||||
`FORCE=1` — on an existing username it RESETS that user's password (and restores `roleId: admin`,
|
||||
so it also rescues a demoted admin).
|
||||
|
||||
```bash
|
||||
# Interactive (preferred — the password never lands in shell history):
|
||||
docker exec -it -e FORCE=1 park-buzi-server-1 node scripts/seed-admin.mjs
|
||||
# → prompts: username (Enter = admin), new password (min 8 chars)
|
||||
|
||||
# Non-interactive (scripted; NB the password enters the HOST's shell history):
|
||||
docker exec -e FORCE=1 -e ADMIN_USER=admin -e ADMIN_PASS='new-strong-pass' \
|
||||
park-buzi-server-1 node scripts/seed-admin.mjs
|
||||
```
|
||||
|
||||
- **Attributable, not gated.** Whoever holds Linux root owns the DB file — the app cannot defend
|
||||
against that actor and doesn't pretend to. What it CAN do: the script appends a **signed
|
||||
`config_change` ledger event** (`admin.passwordReset` / `admin.seeded` on first seed, operator
|
||||
`console:seed-admin`) so a console reset stays visible in the chain afterwards. If the signing key
|
||||
is unavailable (e.g. a dev shell), it warns loudly and proceeds — locking an admin out to protect
|
||||
an audit line would invert the priority. The [[threat-model]] adversary remains the *operator*,
|
||||
who has no Linux account at all.
|
||||
- **Sessions are NOT revoked** by a password reset — issued JWT cookies ride to expiry. A *forgotten*
|
||||
password needs nothing more; a *suspected-stolen* one should also rotate the booth's `JWT_SECRET`
|
||||
(Komodo Variables → redeploy), which invalidates every session instantly.
|
||||
- Works on a fresh/reset DB too (the role-row self-heal above), so §7b first-seed, §7d post-reset
|
||||
re-seed, and this recovery are all the same one command.
|
||||
|
||||
### Healthy startup + web-access
|
||||
|
||||
|
||||
Reference in New Issue
Block a user