feat(identity): F-005 register/login/logout with argon2 sessions and rate limiting
- 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
This commit is contained in:
41
work/artifacts/F-005/implementer.md
Normal file
41
work/artifacts/F-005/implementer.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 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_SECURE` in 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: `down` now uses `count: 0`
|
||||
(full revert) since the suite has two migrations; test asserts identity tables AND
|
||||
app_meta are gone. Helper `runMigrations` gained 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 the `test:integration`
|
||||
script via `node --env-file-if-exists=.env`.
|
||||
Reference in New Issue
Block a user