--- description: "Task list for 013-auth-hardening" --- # Tasks: Authentication Hardening **Input**: Design documents from `specs/013-auth-hardening/` **Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md), [data-model.md](./data-model.md), [contracts/auth-hardening-contract.md](./contracts/auth-hardening-contract.md), [quickstart.md](./quickstart.md) **Organization**: Tasks are grouped by user story (US1 = P1 password reset, US2 = P1 password policy, US3 = P1 login rate-limiting). US2 is a dependency US1's own consume endpoint needs, so build it first despite the nominal priority tie; US3 is independent of both. ## Format: `[ID] [P?] [Story] Description` All file paths are relative to `supporthub-api/` (repo root). --- ## Phase 1: Foundational (Blocking Prerequisites) - [x] T001 Add `PASSWORD_MIN_LENGTH` (default `10`), `PASSWORD_RESET_TOKEN_LIFETIME_MINUTES` (default `30`), `LOGIN_RATE_LIMIT_MAX_ATTEMPTS` (default `5`), and `LOGIN_RATE_LIMIT_WINDOW_SECONDS` (default `300`) to `src/config/env.ts`, exposed via `src/config/auth.ts`'s existing `authConfig` object **Checkpoint**: Config in place. Both user stories can now be built. --- ## Phase 2: User Story 2 - Password strength is enforced wherever a password is set (Priority: P1) **Goal**: One shared validator, called from both the (not-yet-built) reset-consume endpoint and the existing admin account-creation endpoint. **Independent Test**: Quickstart Scenario 2. ### Tests for User Story 2 - [x] T002 [P] [US2] Unit test for `validatePasswordStrength` (too-short rejected with the actual minimum named; policy-meeting password passes) in `tests/unit/identity/password-policy.test.ts` ### Implementation for User Story 2 - [x] T003 [US2] Add `identity/auth/mapper/password-policy.ts`'s `validatePasswordStrength(password): void`, throwing `ValidationError` (depends on T001) - [x] T004 [US2] Export it from `identity/auth`'s public `index.ts` (depends on T003) - [x] T005 [US2] Call it from `identity/agents/service/users.service.ts`'s `UsersService.create`, before hashing (depends on T004) - [x] T006 [US2] Run Quickstart Scenario 2 step 1 locally and confirm it passes; re-run 010-identity-auth's own existing `POST /admin/users` tests to confirm no regression **Checkpoint**: No password shorter than the policy can ever be set via the admin endpoint. --- ## Phase 3: User Story 1 - A user resets a forgotten password (Priority: P1) **Goal**: The full request → stub-delivery → consume → login-with-new-password flow. **Independent Test**: Quickstart Scenario 1. ### Tests for User Story 1 - [x] T007 [US1] Integration test covering Quickstart Scenario 1 (request issues a token via the log stub; a nonexistent email gets an identical response; consume succeeds once and fails the second time; login works with the new password and fails with the old) in `tests/integration/password-reset-flow.test.ts` (depends on T006) ### Implementation for User Story 1 - [x] T008 [US1] Add `identity/auth/mapper/reset-token.ts` — `generateResetToken()` (raw token + its SHA-256 hash) (depends on T001) - [x] T009 [US1] Add `identity/auth/repository/reset-token.repository.ts` — `issue(userId, tokenHash, ttlSeconds)` (deletes any prior token for this user first, per data-model.md's paired-key shape), `resolve(tokenHash)` (returns `userId` or null), `consume(tokenHash, userId)` (deletes both keys) (depends on T008) - [x] T010 [US1] Add `AuthService.requestPasswordReset(email)`: always returns the same public result; internally, if the email resolves to an active account, issues a token and logs the stub delivery event (structured log, research.md) (depends on T009) - [x] T011 [US1] Add `AuthService.resetPassword(token, newPassword)`: validates password strength first (depends on T004), then resolves/consumes the token, 400s with a specific reason if the token is missing/expired/used, hashes and stores the new password (depends on T009, T004) - [x] T012 [US1] Add `POST /auth/password-reset/request` and `POST /auth/password-reset/consume` (both ungated — no session exists yet) in `identity/auth/controller/` + `routes/` + `schema/`, registered from `src/api/routes.ts` (already registers `authRoutes` as a whole, so no new registration call needed — depends on T010, T011) - [x] T013 [US1] Run Quickstart Scenario 1 locally and confirm all 5 steps pass **Checkpoint**: A locked-out user has a real, working self-service fix. --- ## Phase 4: User Story 3 - Login attempts are rate-limited (Priority: P1) **Goal**: `POST /auth/login` throttles repeated attempts per submitted email, checked before any credential verification. **Independent Test**: Quickstart Scenario 3. ### Tests for User Story 3 - [x] T014 [P] [US3] Unit test confirming the rate-limit check is invoked before `repo.findByEmail`/`verifyPassword` in `AuthService.login` (a fake repo/mapper that would throw if called after an already-exceeded limit) in `tests/unit/identity/login-rate-limit-ordering.test.ts` - [x] T015 [US3] Integration test covering Quickstart Scenario 3 (N attempts get 401, the N+1th — even with the correct password — gets 429, a different email is unaffected) in `tests/integration/login-rate-limit.test.ts` (depends on T001) ### Implementation for User Story 3 - [x] T016 [US3] In `AuthService.login`, call the existing `checkRateLimit(`login:${email}`, authConfig.loginRateLimitMaxAttempts, authConfig.loginRateLimitWindowSeconds)` (from `@/infrastructure/cache`) as the very first step, throwing `RateLimitError` if exceeded (depends on T001) - [x] T017 [US3] Run Quickstart Scenario 3 locally and confirm all 3 steps pass **Checkpoint**: All three user stories work independently and together — this feature's full scope. --- ## Phase 5: Polish & Cross-Cutting Concerns - [x] T018 [P] Update `specs/013-auth-hardening/checklists/requirements.md` Notes with any implementation-time findings - [x] T019 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck` - [x] T020 Full regression: `npm run test:unit` then the full integration suite against real Docker-provisioned Postgres/Redis, confirming nothing outside this feature regressed (particularly 010-identity-auth's own login/admin-account tests, now touched by this feature's changes) --- ## Dependencies & Execution Order - **Foundational (Phase 1)**: No dependencies — BLOCKS everything - **User Story 2 (Phase 2)**: Depends on Foundational — BLOCKS User Story 1 (its consume endpoint needs the shared validator) - **User Story 1 (Phase 3)**: Depends on User Story 2 - **User Story 3 (Phase 4)**: Depends only on Foundational — independent of US1/US2, could be built in parallel with either - **Polish (Phase 5)**: Depends on all three