Files
2026-06-18 22:21:38 -04:00

11 KiB

phase, plan, subsystem, tags, status, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags status dependency_graph tech_stack key_files decisions metrics
19-local-auth-no-oidc-mode 05 auth
local-auth
dev-bypass
playwright
e2e
ci
break-glass
option-c
d-15
checkpoint
requires provides affects
issueLocalSessionCookie / getCookie (from 19-01)
local_credentials Drizzle table + 0003 migration (from 19-01)
devAuthBypass() + DEV_USER (from apps/api/src/auth/devBypass.ts)
hashPassword / verifyPassword (from 19-01)
localAuthMiddleware (from 19-03)
devSessionCookieMiddleware()
issues real local-session cookie under bypass (Option C)
apps/api/scripts/reset-admin.ts
break-glass CLI (dev-only, D-13)
apps/pwa/e2e/login.spec.ts
real-login-form e2e spec (AUTH-LOCAL-12/15)
global-setup.ts
local_credentials dev seed (devuser/devpass) + TRUNCATE
ci.yml
LOCAL_SESSION_SECRET + local_credentials seed in harness job
apps/api/src/auth/devBypass.ts (devSessionCookieMiddleware added)
apps/api/src/index.ts (devSessionCookieMiddleware mounted after devAuthBypass)
apps/pwa/e2e/global-setup.ts (TRUNCATE + INSERT local_credentials)
.gitea/workflows/ci.yml (LOCAL_SESSION_SECRET + local_credentials seed step)
apps/api/tests/routes/* (mock devBypass now exports devSessionCookieMiddleware)
added patterns
Option C
devSessionCookieMiddleware issues real JWT cookie under bypass (D-14/D-15)
Production hard-guard FIRST check pattern (mirrors devAuthBypass, T-19-24)
Inline scrypt PHC hashPassword (Pitfall 11 — plain Node.js scripts)
CLI --dry-run flag
validates without writing (T-19-26)
vitest mock update pattern
add new exports to all vi.mock(devBypass.js) blocks
created modified
apps/api/scripts/reset-admin.ts
apps/pwa/e2e/login.spec.ts
apps/api/src/auth/devBypass.ts
apps/api/src/index.ts
apps/pwa/e2e/global-setup.ts
.gitea/workflows/ci.yml
apps/api/tests/lib/requireAdmin.test.ts
apps/api/tests/routes/admin.test.ts
apps/api/tests/routes/authMode.test.ts
apps/api/tests/routes/lists.test.ts
apps/api/tests/routes/localAuth.test.ts
apps/api/tests/routes/push.test.ts
apps/api/tests/routes/setup.test.ts
Option C (devSessionCookieMiddleware): minimal-change path — bypass keeps setting c.get('user') AND issues local-session cookie, so existing specs pass unchanged
devSessionCookieMiddleware degrades gracefully when LOCAL_SESSION_SECRET is absent (skip cookie issuance) rather than throwing
reset-admin uses mysql2/promise createConnection (same as global-setup.ts) — no new deps
CI local_credentials seed step uses inline CJS hashPassword (--input-type=commonjs) matching the existing CI seed pattern
LOCAL_SESSION_SECRET CI value: 'dev-secret-change-me-0000000000000000' — 36 chars, documented as dev-only
login.spec.ts scoped to desktop/Chromium only — other profiles reach the app via bypass cookie unchanged
duration completed tasks_completed tasks_total files_created files_modified
~13 minutes 2026-06-17 3 4 2 11

Phase 19 Plan 05: Dev-Bypass Rework + Harness + CI Summary

One-liner: Option C devSessionCookieMiddleware issues real local-session cookie under DEV_AUTH_BYPASS, break-glass reset-admin CLI, login.spec.ts real-form e2e, global-setup seeds local_credentials, and CI harness job gets LOCAL_SESSION_SECRET.

Status: CHECKPOINT REACHED

Task 4 is a type="checkpoint:human-verify" (gate="blocking"). Tasks 1-3 are complete and committed. The plan pauses for human confirmation that the full Playwright harness + CI run are green and that no dev artifact ships in the published image (D-15 boundary).

Tasks Completed

Task Name Commit Key Files
1 Option C — devSessionCookieMiddleware 3094df8 devBypass.ts, index.ts
2 Break-glass reset-admin CLI 8239187 apps/api/scripts/reset-admin.ts
3 global-setup seed + login.spec.ts + CI env 1f94dc5 global-setup.ts, login.spec.ts, ci.yml + 7 test mocks

Task 4: Checkpoint (Pending Human Verification)

Checkpoint type: human-verify (blocking)

What was verified locally

API tests: 446/446 tests pass (all 34 test files, including devBypass.test.ts: 3/3).

Typecheck: pnpm --filter @familysync/api typecheck and pnpm --filter @familysync/pwa typecheck both exit 0.

reset-admin --dry-run: Exit 0; no password value ("ignored") in output.

D-15 boundary verified:

  • .dockerignore excludes apps/api/scripts/ (reset-admin.ts never ships) — confirmed in file.
  • .dockerignore excludes apps/pwa/e2e/ (global-setup seed never ships) — confirmed in file.
  • devSessionCookieMiddleware() production hard-guard is FIRST check (line 105 of devBypass.ts).
  • reset-admin.ts NODE_ENV=production throw is FIRST executable statement (line 26).
  • LOCAL_SESSION_SECRET in ci.yml is a documented dev-only value, never in the published image.

E2E login.spec.ts: Cannot run locally yet — LoginPage.tsx is being produced by the concurrent plan 04 executor in the same wave. The spec is structurally correct (matches UI-SPEC selectors id="login-username", role="heading" name="Sign in", etc.) and will run as part of the full harness after wave 4 merges.

What the human needs to verify

  1. Push and run CI: Push the branch → confirm the Gitea CI harness job is green. The harness job now includes LOCAL_SESSION_SECRET and the local_credentials seed step. The full Playwright suite (iphone + pixel + desktop) should pass including login.spec.ts on the desktop profile.
  2. D-15 image boundary: Confirm the publish.yml image-hygiene assertion still passes (no apps/api/scripts/ or apps/pwa/e2e/ artifacts in the published image). Spot-check .dockerignore covers both dirs.
  3. Confirm login.spec.ts passes: After wave 4 merges (plan 04 completes LoginPage.tsx), confirm pnpm --filter @familysync/pwa test:e2e --grep "login" exits 0 on the desktop profile.

Resume signal: Type "approved" if the full harness + CI are green and no dev artifact ships.

What Was Built

Task 1: devSessionCookieMiddleware (Option C)

apps/api/src/auth/devBypass.ts — new export devSessionCookieMiddleware(): MiddlewareHandler:

  • Production hard-guard FIRST check: NODE_ENV === 'production' → no-op (T-19-24, D-15)
  • No-op when DEV_AUTH_BYPASS !== 'true'
  • No-op when LOCAL_SESSION_SECRET not set (degrades gracefully)
  • When active: if no local-session cookie present, calls issueLocalSessionCookie(c, DEV_USER.id)
  • Imports: getCookie from hono/cookie, issueLocalSessionCookie from localSession.ts

apps/api/src/index.ts — mounts devSessionCookieMiddleware() immediately after devAuthBypass() on /api/*.

Task 2: reset-admin.ts (Break-Glass CLI)

apps/api/scripts/reset-admin.ts — standalone break-glass CLI (149 lines):

  • NODE_ENV=production throw as FIRST executable statement (D-13/D-15)
  • .dockerignore apps/api/scripts/ excludes it from the prod image (IMG-02)
  • Inline scrypt PHC hashPassword() (Pitfall 11 — cannot import compiled TS from plain script)
  • Parses --username / --password / --dry-run from process.argv
  • Upserts users row (is_admin=true, claimed=true) then upserts local_credentials row
  • Never logs the password value (T-19-26)
  • --dry-run: validates args + DB connection without writing; exit 0

Task 3: global-setup seed + login.spec.ts + CI harness env

apps/pwa/e2e/global-setup.ts:

  • Added hashPasswordInline() inline scrypt PHC (Pitfall 11 — plain Node.js)
  • Added TRUNCATE TABLE local_credentials to the TRUNCATE block
  • Added INSERT INTO local_credentials (user_id, username, password_hash) VALUES (1, 'devuser', ?) ON DUPLICATE KEY UPDATE ... after member_credentials seed

apps/pwa/e2e/login.spec.ts (new, 98 lines):

  • Scoped to desktop/Chromium only (other profiles use bypass cookie)
  • Uses context.clearCookies() before each test to strip the bypass-issued cookie
  • Test 1: unauthenticated navigation → /login; brand + "Sign in" heading + form visible
  • Test 2: wrong password → role="status" shows "Incorrect username or password."
  • Test 3: devuser/devpass → navigates away from /login

.gitea/workflows/ci.yml harness job:

  • Added new "Seed local_credentials for dev user (id=1)" step (CJS inline script with hashPassword)
  • Added LOCAL_SESSION_SECRET: 'dev-secret-change-me-0000000000000000' to harness env
  • LOCAL_SESSION_SECRET is a dev-only value, never in the published image (IMG gates)

Test mock fixes (Rule 1 — Bug): Added devSessionCookieMiddleware: () => async (_c, next) => next() to all 7 vi.mock('../../src/auth/devBypass.js', ...) blocks that used an explicit factory return object (admin, setup, push, lists, localAuth, authMode, requireAdmin tests). events.test.ts uses importOriginal + spread and already picks up the new export automatically.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] vitest mock missing devSessionCookieMiddleware export

  • Found during: Task 3 — running the full API test suite after Task 1's devBypass.ts change
  • Issue: 7 test files mock devBypass.js with an explicit factory object. After adding devSessionCookieMiddleware to devBypass.ts, vitest reported "No devSessionCookieMiddleware export is defined on the mock" for every mock that did not include it.
  • Fix: Added devSessionCookieMiddleware: () => async (_c, next) => next() to all 7 explicit mock factories: admin.test.ts, setup.test.ts, push.test.ts (both vi.mock and vi.doMock), lists.test.ts, localAuth.test.ts, authMode.test.ts, requireAdmin.test.ts.
  • Files modified: 7 test files
  • Commit: 1f94dc5

D-15 Guarantee

Artifact Dev boundary Enforcement
devSessionCookieMiddleware NODE_ENV=production hard-guard (FIRST check) + IMG-01 boot guard T-19-24
reset-admin.ts NODE_ENV=production throw (FIRST statement) + .dockerignore apps/api/scripts/ T-19-25, IMG-02
local_credentials dev seed Lives in apps/pwa/e2e/global-setup.ts (.dockerignore apps/pwa/e2e/) + CI step only T-19-23
LOCAL_SESSION_SECRET in CI Dev-only value in harness job env; never in Dockerfile or published image IMG-01/02/03

Known Stubs

None. All new code performs real operations.

Threat Surface Scan

No new network endpoints introduced. New surface:

  • devSessionCookieMiddleware: internal middleware, no external exposure; guarded by NODE_ENV=production FIRST check (T-19-24).
  • reset-admin.ts: CLI only (docker exec), guarded by NODE_ENV=production throw + .dockerignore exclusion (T-19-25).

All surfaces are within the plan's threat model (T-19-23 through T-19-26).

Self-Check: PASSED

All created files confirmed present on disk:

  • FOUND: apps/api/scripts/reset-admin.ts
  • FOUND: apps/pwa/e2e/login.spec.ts

All commits confirmed in git log:

  • 3094df8: feat(19-05): Option C — devSessionCookieMiddleware issues real local-session cookie under bypass
  • 8239187: feat(19-05): add break-glass reset-admin CLI (dev-only, .dockerignore'd)
  • 1f94dc5: feat(19-05): global-setup local_credentials seed + login.spec.ts + CI harness env

API tests: 446/446 pass (all 34 test files); typecheck: exit 0.