chore: merge executor worktree (worktree-agent-ad5628ea37541e256)
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
---
|
||||
phase: 19-local-auth-no-oidc-mode
|
||||
plan: "05"
|
||||
subsystem: auth
|
||||
tags: [local-auth, dev-bypass, playwright, e2e, ci, break-glass, option-c, d-15]
|
||||
status: checkpoint
|
||||
dependency_graph:
|
||||
requires:
|
||||
- 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)
|
||||
provides:
|
||||
- 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
|
||||
affects:
|
||||
- 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)
|
||||
tech_stack:
|
||||
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
|
||||
key_files:
|
||||
created:
|
||||
- apps/api/scripts/reset-admin.ts
|
||||
- apps/pwa/e2e/login.spec.ts
|
||||
modified:
|
||||
- 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
|
||||
decisions:
|
||||
- "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"
|
||||
metrics:
|
||||
duration: "~13 minutes"
|
||||
completed: "2026-06-17"
|
||||
tasks_completed: 3
|
||||
tasks_total: 4
|
||||
files_created: 2
|
||||
files_modified: 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.
|
||||
Reference in New Issue
Block a user