Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.8 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 | 01 | auth |
|
checkpoint |
|
|
|
|
|
Phase 19 Plan 01: Local Auth Foundation Summary
One-liner: Scrypt password primitives, stateless local-session JWT cookie helpers, local_credentials MariaDB table + additive migration, LOCAL_SESSION_SECRET boot guard wired in index.ts, and .dockerignore break-glass script exclusion.
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 review of the generated migration SQL before proceeding.
Tasks Completed
| Task | Name | Commit | Key Files |
|---|---|---|---|
| 1 (RED) | hashPassword/verifyPassword tests | 7ece966 |
apps/api/tests/auth/localCredentials.test.ts |
| 1 (GREEN) | hashPassword/verifyPassword implementation | 85b01b5 |
apps/api/src/auth/localCredentials.ts |
| 2 (RED) | localSession + bootGuards tests | 0d8f3fa |
apps/api/tests/auth/localSession.test.ts |
| 2 (GREEN) | localSession + bootGuards + index.ts | 7d61148 |
apps/api/src/auth/localSession.ts, bootGuards.ts, index.ts |
| 3 | schema + migration + secrets + dockerignore | 96f0991 |
schema.ts, 0003_warm_deathstrike.sql, generate-secrets.mjs, .dockerignore |
Task 4: Checkpoint (Pending Human Review)
Checkpoint type: human-verify (blocking)
The migration 0003_warm_deathstrike.sql was generated by drizzle-kit generate and applied to the dev DB with pnpm --filter @familysync/api db:migrate (exit 0). It contains:
CREATE TABLE `local_credentials` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` int NOT NULL,
`username` varchar(128) NOT NULL,
`password_hash` varchar(256) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT (now()),
`updated_at` timestamp DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `local_credentials_id` PRIMARY KEY(`id`),
CONSTRAINT `uniq_local_cred_user` UNIQUE(`user_id`),
CONSTRAINT `uniq_local_cred_username` UNIQUE(`username`)
);
--> statement-breakpoint
ALTER TABLE `local_credentials` ADD CONSTRAINT `local_credentials_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_local_credentials_user_id` ON `local_credentials` (`user_id`);
The SQL is purely additive. No ALTER/DROP/TRUNCATE/RENAME touches any existing table.
What the human needs to verify:
- Review
apps/api/src/db/migrations/0003_warm_deathstrike.sql— confirm onlyCREATE TABLE local_credentials(no statements touching users, member_credentials, calendars, calendar_events, app_config, or any other existing table). - Confirm
LOCAL_SESSION_SECRETis set in your local.env(>=32 chars) — without it the API will refuse to boot in non-bypass mode. Add it vianode scripts/generate-secrets.mjsif not present.
Resume signal: Type "approved" if migration is purely additive and LOCAL_SESSION_SECRET is set.
What Was Built
Task 1: hashPassword/verifyPassword (TDD)
apps/api/src/auth/localCredentials.ts exports:
hashPassword(password: string): string— scrypt + 16-byte random salt, returns PHC-encoded stringverifyPassword(storedEncoded: string, candidate: string): boolean— timingSafeEqual, never throws
Zero new npm dependencies. All 5 unit tests pass (round-trip, wrong-password, unique-salt, malformed-hash, PHC-shape).
Task 2: localSession.ts + boot guard (TDD)
apps/api/src/auth/localSession.ts exports:
issueLocalSessionCookie(c, userId)— signs JWT (HS256) with LOCAL_SESSION_SECRET, sets httpOnly cookieverifyLocalSessionCookie(c)— returns userId or null (never throws, catches Jwt.verify expiry throws)clearLocalSessionCookie(c)— deletes the cookie with matching attributes
apps/api/src/lib/bootGuards.ts adds:
assertLocalSessionSecretSet()— exits with FATAL if secret missing/<32 chars when not in bypass mode
apps/api/src/index.ts — assertLocalSessionSecretSet() called immediately after assertNotDevBypassInProduction().
All 5 unit tests pass; pnpm --filter @familysync/api typecheck exits 0.
Task 3: Schema + Migration + Secrets + .dockerignore
apps/api/src/db/schema.ts—localCredentialstable exported (UNIQUE user_id, UNIQUE username, FK->users cascade)apps/api/src/db/migrations/0003_warm_deathstrike.sql— purely additive CREATE TABLE; applied to dev DBapps/api/test/setup.ts—localCredentialsadded to afterEach cleanup (FK-safe ordering)scripts/generate-secrets.mjs— emitsLOCAL_SESSION_SECRET(base64 32-byte, 44 chars).dockerignore— addedapps/api/scripts/directory exclusion (D-15/IMG-02)
Deviations from Plan
Auto-fixed Issues
None. Plan executed as written.
Notes
- The migration file generated by drizzle-kit is named
0003_warm_deathstrike.sql(drizzle-kit generates random animal names for migrations). The plan referenced0003_local_credentials.sqlas an expected name — this is not a semantic deviation, only a filename difference from drizzle-kit's naming convention. The content and purpose match exactly. pnpm --filter @familysync/api db:migratewas run against the dev stack DB (credentials from.env). The worktree shares the main repo's dev DB connection, which is expected and safe for an additive migration.- Tests requiring MariaDB were run with
CI=trueto bypass the global-setup root-DB-provisioning step (which requires a root MySQL connection that isn't available from the worktree's network context). Pure unit tests (no DB access) work correctly in this mode.
Threat Surface Scan
No new network endpoints introduced in this plan. All new surface is internal stdlib / crypto utilities and a DB table migration. No changes to trust boundaries that aren't already covered by the plan's threat model (T-19-01 through T-19-04 and T-19-SC).
Known Stubs
None. This plan provides foundational utilities without UI or stub placeholders.
Self-Check: PASSED
All created files confirmed present on disk:
- FOUND: apps/api/src/auth/localCredentials.ts
- FOUND: apps/api/src/auth/localSession.ts
- FOUND: apps/api/src/db/migrations/0003_warm_deathstrike.sql
- FOUND: apps/api/tests/auth/localCredentials.test.ts
- FOUND: apps/api/tests/auth/localSession.test.ts
All commits confirmed in git log:
7ece966: test(19-01): add failing tests for hashPassword/verifyPassword85b01b5: feat(19-01): implement hashPassword/verifyPassword0d8f3fa: test(19-01): add failing tests for localSession7d61148: feat(19-01): implement localSession JWT cookie helpers96f0991: feat(19-01): schema + migration + secrets + dockerignore