- Hexagonal identity module: domain ports, use cases, argon2id hasher, pg repos - Migration 002_identity: identity_users + identity_sessions (token hash only) - Opaque 512-bit session tokens; DB stores SHA-256 hash; 7-day TTL in SQL - Cookie HttpOnly + Secure (COOKIE_SECURE, default true) + SameSite=Lax - LoginRateLimiter: 10 failures -> 429 + Retry-After, 15-min cooldown - Anti-enumeration: identical generic 401 + dummy-hash timing equalization - buildApp gains optional pool/cookieSecure; foundation-only app preserved - 47 unit + 14 integration tests; live smoke covers all acceptance criteria
2.2 KiB
2.2 KiB
Implementer — F-005 Identity: register, login, sessions
done -> work/artifacts/F-005/implementer.md
Delivered
- Migration
migrations/002_identity.js:identity_users+identity_sessions(token_hash UNIQUE, FK ON DELETE CASCADE, user_id index). Reversible. - Hexagonal module
src/modules/identity:- domain: user.ts, session.ts (7-day TTL), errors.ts, ports.ts
- application: register-user.ts, login.ts, logout.ts, login-rate-limiter.ts
- infrastructure: argon2-password-hasher.ts (argon2id m=19456 t=2 p=1), pg-user-repository.ts, pg-session-repository.ts, session-token.ts
- api: identity.routes.ts (zod via parseJson, envelope v2 mapping)
- Foundation wiring:
createPool(connectionString),COOKIE_SECUREin config (default true),buildApp({ pool, cookieSecure }), server creates pool from config.
Deps added (justified in spec/tech.md)
- argon2, @fastify/cookie
Evidence
npm run lint✅ (prettier clean after format pass)npm run lint:boundaries✅ (39 files, R1/R2 OK — module tests stay in-subtree, DB integration tests live in src/app/tests)npm run typecheck/npm run build✅npm test: 46 passed, 14 skipped (integration) ✅npm run test:integration: 14/14 ✅ (register argon2-only storage, dup 409, invalid 400, cookie flags, identical 401 bodies, revocation + idempotent logout, 10 failures → 429 + Retry-After, foundation-only app keeps /auth 404)- Live smoke (curl): 201 register / 200 login +
mdv_session …; HttpOnly; Secure; SameSite=Lax; Max-Age=604800/ 401 identical for wrong-password vs unknown-email / 204 logout clears cookie / 10×401 → 429 +retry-after: 900 ./scripts/verify.sh✅
Notes / deviations
- F-002 migrations.itest baseline rollback test updated:
downnow usescount: 0(full revert) since the suite has two migrations; test asserts identity tables AND app_meta are gone. HelperrunMigrationsgained an optional count param. - Rate limiter is in-memory per instance (documented in DESIGN risks; interface ready for Redis swap).
project/.env(gitignored) supplies TEST_DATABASE_URL to thetest:integrationscript vianode --env-file-if-exists=.env.