v0.71.0 Admin panel — users table, login/sessions, generic DB CRUD modal #148

Merged
kamilkrzywda merged 2 commits from feat/admin-panel into master 2026-09-24 12:11:46 +02:00
Owner

What

A new Admin button on the right of the top bar opens a reusable modal (components/Modal.tsx — z-index 60 overlay above the z-50 toasts; role="dialog", closes on Escape/backdrop) hosting components/AdminPanel.tsx. No new URL routes.

Changes

  • users + sessions tables (raw DDL in lib/db/client.ts + typed Drizzle entries in lib/db/schema.ts). Idempotent seeded admin inserted on every open() — user id admin-kamilkrzywda, username kamilkrzywda, admin=1, hash-only (WHERE NOT EXISTS guard; safe on the existing production DB). The users table is also the foundation for multiplayer auth (#105/#107).
  • Auth API — POST /api/login (scrypt + timingSafeEqual; unknown usernames run one full scrypt against a fixed dummy digest so timing stays uniform), POST /api/logout (idempotent), GET /api/me. Sessions are 7-day UUID rows sent as the X-Flight-Session header; the client stores the id in localStorage (flight.session).
  • Generic whole-database CRUD behind the admin flag — lib/auth.ts guard (401 no/expired session, 403 non-admin) runs before any input is touched on GET /api/admin/tables and GET|PUT|POST|DELETE /api/admin/tables/{name}/rows. Rows carry their rowid as __rowid so the PK-less tables (account, sim_state) are editable. SQL safety: table names quoted only after sqlite_master validation, columns validated against PRAGMA table_info, values always bound as parameters.
  • UI — login form / "not an admin" notice / grid (table list → editable rows with per-row Save/Delete, "+ Add row", Refresh; logout in the header area). Deliberately plain dev-tool surface.
  • Test hook — POST /api/test/admin-session (FLIGHT_TEST_HOOKS=1 gate) mints a session for the seeded admin server-side so e2e never needs the real password.
  • OpenAPI — five new paths + schemas; endpoint count twelve → seventeen in every assertion (lib/openapi.test.ts, e2e/api.spec.ts).

Tests

  • Unit: idempotent admin seed (fresh DB / reopen / legacy pre-v0.71.0 file), login success/wrong-password/unknown-username with an in-test scrypt hash for a self-created user, logout idempotency, /api/me matrix, guard ladder (401/401/403), full CRUD validation matrix (rows incl. __rowid, PUT verified by re-fetch, POST+DELETE round-trip, 404 unknown table, 400 unknown column/bad limit).
  • E2E (e2e/admin.spec.ts, CI-only): modal → login form → wrong-creds error → hook-minted session via localStorage → table list incl. packages/users → edit a cell + Save verified by an independent re-fetch → add + delete a users row → logout back to the login form.

Local verification: tsc --noEmit clean, vitest run 410/410 (38 files), playwright test --list 64 tests in 17 files. Full e2e runs in CI only per AGENTS.md.

## What A new **Admin** button on the right of the top bar opens a reusable modal (`components/Modal.tsx` — z-index 60 overlay above the z-50 toasts; `role="dialog"`, closes on Escape/backdrop) hosting `components/AdminPanel.tsx`. No new URL routes. ## Changes - **users + sessions tables** (raw DDL in `lib/db/client.ts` + typed Drizzle entries in `lib/db/schema.ts`). Idempotent seeded admin inserted on every `open()` — user id `admin-kamilkrzywda`, username `kamilkrzywda`, admin=1, hash-only (`WHERE NOT EXISTS` guard; safe on the existing production DB). The users table is also the foundation for multiplayer auth (#105/#107). - **Auth API** — `POST /api/login` (scrypt + timingSafeEqual; unknown usernames run one full scrypt against a fixed dummy digest so timing stays uniform), `POST /api/logout` (idempotent), `GET /api/me`. Sessions are 7-day UUID rows sent as the `X-Flight-Session` header; the client stores the id in localStorage (`flight.session`). - **Generic whole-database CRUD behind the admin flag** — `lib/auth.ts` guard (401 no/expired session, 403 non-admin) runs before any input is touched on `GET /api/admin/tables` and `GET|PUT|POST|DELETE /api/admin/tables/{name}/rows`. Rows carry their rowid as `__rowid` so the PK-less tables (account, sim_state) are editable. SQL safety: table names quoted only after sqlite_master validation, columns validated against PRAGMA table_info, values always bound as parameters. - **UI** — login form / "not an admin" notice / grid (table list → editable rows with per-row Save/Delete, "+ Add row", Refresh; logout in the header area). Deliberately plain dev-tool surface. - **Test hook** — `POST /api/test/admin-session` (FLIGHT_TEST_HOOKS=1 gate) mints a session for the seeded admin server-side so e2e never needs the real password. - **OpenAPI** — five new paths + schemas; endpoint count twelve → seventeen in every assertion (`lib/openapi.test.ts`, `e2e/api.spec.ts`). ## Tests - Unit: idempotent admin seed (fresh DB / reopen / legacy pre-v0.71.0 file), login success/wrong-password/unknown-username with an in-test scrypt hash for a self-created user, logout idempotency, /api/me matrix, guard ladder (401/401/403), full CRUD validation matrix (rows incl. `__rowid`, PUT verified by re-fetch, POST+DELETE round-trip, 404 unknown table, 400 unknown column/bad limit). - E2E (`e2e/admin.spec.ts`, CI-only): modal → login form → wrong-creds error → hook-minted session via localStorage → table list incl. packages/users → edit a cell + Save verified by an independent re-fetch → add + delete a users row → logout back to the login form. Local verification: `tsc --noEmit` clean, `vitest run` 410/410 (38 files), `playwright test --list` 64 tests in 17 files. Full e2e runs in CI only per AGENTS.md.
kamilkrzywda added 1 commit 2026-09-24 11:33:46 +02:00
v0.71.0: admin panel — users table, login/sessions, generic DB CRUD modal
CI / build + unit + e2e (pull_request) Failing after 13m35s
b89d351587
kamilkrzywda scheduled this pull request to auto merge when all checks succeed 2026-09-24 11:40:55 +02:00
kamilkrzywda added 1 commit 2026-09-24 11:56:06 +02:00
v0.71.0: fix admin e2e — pass sessionId into page.evaluate
CI / build + unit + e2e (pull_request) Successful in 15m37s
e24a5788c0
kamilkrzywda merged commit fe23c9ff0f into master 2026-09-24 12:11:46 +02:00
Sign in to join this conversation.