docs(013-auth-hardening): task breakdown
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
52f1fa3db0
commit
65175a85b5
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
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)
|
||||||
|
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] T003 [US2] Add `identity/auth/mapper/password-policy.ts`'s
|
||||||
|
`validatePasswordStrength(password): void`, throwing `ValidationError` (depends on T001)
|
||||||
|
- [ ] T004 [US2] Export it from `identity/auth`'s public `index.ts` (depends on T003)
|
||||||
|
- [ ] T005 [US2] Call it from `identity/agents/service/users.service.ts`'s `UsersService.create`,
|
||||||
|
before hashing (depends on T004)
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] T008 [US1] Add `identity/auth/mapper/reset-token.ts` — `generateResetToken()` (raw token +
|
||||||
|
its SHA-256 hash) (depends on T001)
|
||||||
|
- [ ] 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)
|
||||||
|
- [ ] 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)
|
||||||
|
- [ ] 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)
|
||||||
|
- [ ] 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)
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] 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`
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] 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)
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] T018 [P] Update `specs/013-auth-hardening/checklists/requirements.md` Notes with any
|
||||||
|
implementation-time findings
|
||||||
|
- [ ] T019 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck`
|
||||||
|
- [ ] 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
|
||||||
Reference in New Issue
Block a user