Files
support_backend/specs/013-auth-hardening/tasks.md
T
saqib mirandClaude Sonnet 5 79bc2ef25b feat(013-auth-hardening): password reset, password strength policy, login rate-limiting
Closes the two gaps 010-identity-auth explicitly deferred (password reset,
login rate-limiting), plus a shared password-strength validator both the
reset-consume endpoint and admin account creation now depend on.

- Password reset: single-use, paired-Redis-key tokens (never in Postgres),
  identical response regardless of account existence, stubbed delivery via
  a structured log line (no email infrastructure exists yet).
- Password strength: one validatePasswordStrength() call site, wired into
  both POST /admin/users and the reset-consume flow.
- Login rate-limiting: checkRateLimit keyed by submitted email, checked
  before any credential verification.

Also fixes tests/helpers/auth.ts's shared loginAs() helper, which reused
two fixed accounts across the whole integration suite via upsert — now
rate-limited per email, that collided across ~30 files sharing one budget.
Each call now gets a unique email; no call sites needed to change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 21:22:49 +05:30

6.9 KiB

description
description
Task list for 013-auth-hardening

Tasks: Authentication Hardening

Input: Design documents from specs/013-auth-hardening/

Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/auth-hardening-contract.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.tsgenerateResetToken() (raw token + its SHA-256 hash) (depends on T001)
  • T009 [US1] Add identity/auth/repository/reset-token.repository.tsissue(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