From 8327fafac2b9b49e4c6e85ac62161b81fc1b7008 Mon Sep 17 00:00:00 2001 From: saqib mir Date: Mon, 7 Sep 2026 11:15:45 +0530 Subject: [PATCH] tasks: task breakdown for identity and authentication feature (010) 36 tasks across 8 phases (5 user stories + setup/foundational/polish). US1 (real login) and US2 (real route/role gating) are the P1 MVP; the one task that touches code outside identity/* (T020, adding requireRole('ADMIN') across 002-009's existing admin routes) is called out explicitly to run each touched module's own test suite immediately after, not only in the final regression pass. Co-Authored-By: Claude Sonnet 5 --- specs/010-identity-auth/tasks.md | 285 +++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 specs/010-identity-auth/tasks.md diff --git a/specs/010-identity-auth/tasks.md b/specs/010-identity-auth/tasks.md new file mode 100644 index 0000000..3c5bb75 --- /dev/null +++ b/specs/010-identity-auth/tasks.md @@ -0,0 +1,285 @@ +--- +description: "Task list for 010-identity-auth" +--- + +# Tasks: Identity and Authentication + +**Input**: Design documents from `specs/010-identity-auth/` + +**Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md), +[data-model.md](./data-model.md), +[contracts/identity-auth-contract.md](./contracts/identity-auth-contract.md), +[quickstart.md](./quickstart.md) + +**Tests**: Included as first-class tasks. Pure logic worth a unit test: the identical-failure- +response behavior (FR-002/SC-003) and the `requireRole` matching logic. Everything else is +best proven end-to-end against a real Postgres/Redis, including a specific pass re-verifying +existing 002-009 admin routes now actually reject an invalid session. + +**Organization**: Tasks are grouped by user story (US1 = P1 login, US2 = P1 route/role gating, +US3 = P2 self-identity, US4 = P2 admin-created accounts, US5 = P3 logout). + +## Format: `[ID] [P?] [Story] Description` + +All file paths are relative to `supporthub-api/` (repo root). + +--- + +## Phase 1: Setup + +- [ ] T001 [P] Add `jsonwebtoken` and `bcryptjs` (plus `@types/jsonwebtoken`, + `@types/bcryptjs`) to `package.json` +- [ ] T002 [P] Add `AUTH_JWT_SECRET` (required, no default — never a committed secret) and + `AUTH_TOKEN_LIFETIME_HOURS` (`z.coerce.number().default(4)`) to `src/config/env.ts`, + exposed via a new `src/config/auth.ts` (`authConfig.jwtSecret`, + `authConfig.tokenLifetimeHours`), matching `orchestrationConfig`'s exact shape +- [ ] T003 [P] Populate `src/modules/identity/auth/` with the full standard shape around its + existing files, replacing the email-only `AuthService.validateCredentials`/ + `AuthRepository.findByEmail`-only stub content + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Schema for the entities every user story needs. + +**⚠️ CRITICAL**: No user-story stage work can begin until this phase is complete. + +- [ ] T004 Add `User.passwordHash` (`String`, required) and `User.active` (`Boolean + @default(true)`) to `prisma/schema.prisma`, plus `Agent.userId` (`String? @unique`, FK to + `User.id` — research.md's additive, not-yet-consumed link) (depends on T001-T003) +- [ ] T005 Run `npm run prisma:generate` and create the migration (`npm run prisma:migrate`) + for T004 (depends on T004) +- [ ] T006 Update `prisma/seed/roles.seed.ts` to set a real bcryptjs-hashed password on both + seeded accounts (`admin@supporthub.internal`, `agent@supporthub.internal`), documenting + the plaintext dev password in a comment directly above the hash call (local/dev use only, + per spec.md Edge Cases) (depends on T005) + +**Checkpoint**: Schema migrated, demo accounts have real passwords. User stories can now be +built. + +--- + +## Phase 3: User Story 1 - An agent or admin logs in and receives a session (Priority: P1) 🎯 MVP (part 1) + +**Goal**: Real password verification and JWT issuance, with an identical failure response +regardless of which reason login failed. + +**Independent Test**: Quickstart Scenario 1. + +### Tests for User Story 1 + +- [ ] T007 [P] [US1] Unit test: given a found user with a matching/non-matching password, and + given no user found at all, the login-failure path produces byte-identical response + shape/status in the non-matching and no-user cases — in + `tests/unit/identity/login-failure-parity.test.ts` +- [ ] T008 [US1] Integration test covering Quickstart Scenario 1 (correct login succeeds with a + token + identity; wrong password and nonexistent email produce the same `401`) against a + real Postgres in `tests/integration/identity-auth-flow.test.ts` (depends on T006) + +### Implementation for User Story 1 + +- [ ] T009 [US1] Add `hashPassword`/`verifyPassword` (bcryptjs) and `signToken`/`verifyToken` + (jsonwebtoken, embedding `sub`/`email`/`role`/`actorType`/`jti`/`iat`/`exp` per + data-model.md) in `identity/auth/mapper/` (depends on T002) +- [ ] T010 [US1] Add `AuthRepository.findActiveByEmail` (replacing `findByEmail`) in + `identity/auth/repository/` (depends on T005) +- [ ] T011 [US1] Add `AuthService.login(email, password)`: looks up the user, compares against + either the found hash or a fixed dummy hash when not found (FR-002's timing/shape + parity), returns `{ token, user }` or throws a single, identical `AuthenticationError` for + every failure branch — in `identity/auth/service/` (depends on T009, T010) +- [ ] T012 [US1] Replace `POST /auth/login`'s schema (`email` + `password`, replacing the + email-only schema) and controller in `identity/auth/schema/` + `controller/`, registered + from `src/api/routes.ts` (depends on T011) +- [ ] T013 [US1] Run Quickstart Scenario 1 locally and confirm all 3 steps pass + +**Checkpoint**: Login works and never leaks account existence through its failure response. + +--- + +## Phase 4: User Story 2 - Protected routes require a valid session; admin-only routes require the admin role (Priority: P1) 🎯 MVP (part 2) + +**Goal**: `fastify.authenticate` actually verifies; `requireRole` enforces role on top of it; +every existing gated route is re-verified. + +**Independent Test**: Quickstart Scenario 2. + +### Tests for User Story 2 + +- [ ] T014 [P] [US2] Unit test for `requireRole`'s matching logic (allowed role passes, wrong + role throws `AuthorizationError`, no `request.user` at all throws) in + `tests/unit/identity/require-role.test.ts` +- [ ] T015 [US2] Integration test covering Quickstart Scenario 2 (no header, malformed token, + wrong-role token, correct-role token) against a real Postgres/Redis in + `tests/integration/identity-auth-flow.test.ts` (depends on T008) +- [ ] T016 [US2] Integration test spot-checking at least one existing admin route per module + (002's product-integration admin route, 004's knowledge admin route, 006's team-creation + route, 007's manual-assignment route, 008's SLA-policy route, 009's investigation route) + now rejects a missing/invalid session — in `tests/integration/identity-auth-flow.test.ts` + (depends on T015) + +### Implementation for User Story 2 + +- [ ] T017 [US2] Add revocation-denylist helpers (`isTokenRevoked`, `revokeToken`) in + `src/infrastructure/cache/`, alongside the existing `hasSeenJti`/`markJtiSeen` (same + Redis-key-with-TTL shape, research.md) (depends on T002) +- [ ] T018 [US2] Replace `auth.plugin.ts`'s `authenticate` stub: verify the JWT signature and + expiry, check T017's revocation denylist, and on success set `request.user` (the full + `AuthUser`) and `request.reqContext.actorId`/`actorType` — throw `AuthenticationError` on + any failure, never pass through as anonymous (depends on T009, T017) +- [ ] T019 [US2] Add `requireRole(...allowedRoles: string[])` preHandler factory (checks + `request.user?.role`, throws `AuthorizationError` if it doesn't match) in + `identity/auth/service/` (or a dedicated `identity/auth/guards/` file), exported from + `identity/auth`'s public `index.ts` (depends on T018) +- [ ] T020 [US2] Add `requireRole('ADMIN')` to every existing write/config admin route across + 002-009 that doesn't already distinguish agent-vs-admin access (product-integration + admin, knowledge admin, teams/hierarchy admin, SLA/escalation-policy admin) — read-only + routes and ticket-working routes an agent legitimately uses stay `fastify.authenticate`- + only (research.md's own scoping: this is a mechanical pass applying an existing judgment, + not a new design) (depends on T019) +- [ ] T021 [US2] Run Quickstart Scenario 2 locally and confirm all 4 steps pass + +**Checkpoint**: Every P1 user story is complete — a session is real, and it's actually checked +everywhere it's supposed to be. This is the feature's MVP. + +--- + +## Phase 5: User Story 3 - An authenticated user can identify themselves (Priority: P2) + +**Goal**: A self-identity endpoint that re-validates against current account state. + +**Independent Test**: Quickstart Scenario 3. + +### Tests for User Story 3 + +- [ ] T022 [US3] Integration test covering Quickstart Scenario 3 (identity matches login; + deactivating the account rejects a still-unexpired token's use of this endpoint + specifically) — in `tests/integration/identity-auth-flow.test.ts` (depends on T021) + +### Implementation for User Story 3 + +- [ ] T023 [US3] Add `AuthService.getCurrentUser(userId)`: re-fetches the `User` row, throws + `AuthenticationError` if it no longer exists or `active: false` — in `identity/auth/ + service/` (depends on T010) +- [ ] T024 [US3] Add `GET /auth/me` route (gated by `fastify.authenticate`) in `identity/auth/ + controller/` + `routes/` (depends on T023) +- [ ] T025 [US3] Run Quickstart Scenario 3 locally and confirm both steps pass + +**Checkpoint**: A session can be introspected and is re-validated against live account state. + +--- + +## Phase 6: User Story 4 - An admin creates additional agent/admin accounts (Priority: P2) + +**Goal**: Admin-only account creation, immediately usable to log in. + +**Independent Test**: Quickstart Scenario 4. + +### Tests for User Story 4 + +- [ ] T026 [US4] Integration test covering Quickstart Scenario 4 (admin creates an account and + it logs in immediately; non-admin rejected; duplicate email rejected) — in + `tests/integration/identity-auth-flow.test.ts` (depends on T021) + +### Implementation for User Story 4 + +- [ ] T027 [US4] Add `UsersService.create(email, name, role, password)` (resolve-or-409 on + duplicate email, hashes the password via T009) in `identity/agents/service/` (research.md + — account creation lives alongside `identity/agents`'s own roster CRUD, not + `identity/auth`) (depends on T009) +- [ ] T028 [US4] Add `POST /admin/users` route (gated by `fastify.authenticate` + + `requireRole('ADMIN')`) in `identity/agents/controller/` + `routes/`, registered from + `src/api/routes.ts` — response never includes the password or hash (depends on T019, T027) +- [ ] T029 [US4] Run Quickstart Scenario 4 locally and confirm all 4 steps pass + +**Checkpoint**: New staff accounts can be provisioned without a manual database write. + +--- + +## Phase 7: User Story 5 - A user logs out (Priority: P3) + +**Goal**: Explicit, immediate session revocation. + +**Independent Test**: Quickstart Scenario 5. + +### Tests for User Story 5 + +- [ ] T030 [US5] Integration test covering Quickstart Scenario 5 (logout succeeds; the same + token is rejected immediately afterward) — in `tests/integration/identity-auth-flow.test.ts` + (depends on T021) + +### Implementation for User Story 5 + +- [ ] T031 [US5] Add `AuthService.logout(jti, remainingTtlSeconds)`: calls T017's `revokeToken` + — in `identity/auth/service/` (depends on T017) +- [ ] T032 [US5] Add `POST /auth/logout` route (gated by `fastify.authenticate`) in + `identity/auth/controller/` + `routes/` (depends on T031) +- [ ] T033 [US5] Run Quickstart Scenario 5 locally and confirm both steps pass + +**Checkpoint**: All five user stories work independently and together — real login, real +gating, self-identity, admin-provisioned accounts, and logout form one coherent auth system. + +--- + +## Phase 8: Polish & Cross-Cutting Concerns + +- [ ] T034 [P] Update `specs/010-identity-auth/checklists/requirements.md` Notes with any + implementation-time findings +- [ ] T035 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck` +- [ ] T036 Full regression: `npm run test:unit` (scoped to `tests/unit`) to confirm nothing + broke elsewhere, then the full integration suite (including 002-009's own suites, since + T020 adds `requireRole` to their existing routes) against real Docker-provisioned + Postgres/Redis + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies +- **Foundational (Phase 2)**: Depends on Setup — BLOCKS all user stories +- **User Story 1 (Phase 3)**: Depends on Foundational — no dependency on US2-US5 +- **User Story 2 (Phase 4)**: Depends on US1 (a real token to verify) +- **User Story 3 (Phase 5)**: Depends on US2 (the gate US3's own route sits behind) +- **User Story 4 (Phase 6)**: Depends on US2 (`requireRole('ADMIN')`) +- **User Story 5 (Phase 7)**: Depends on US2 (the gate logout's own route sits behind) and + US1's token shape (`jti`) +- **Polish (Phase 8)**: Depends on all five user stories + +### Parallel Opportunities + +- T001-T003 (independent scaffolding) +- T007 (unit test) alongside T009-T011 (the implementation it tests) +- T014 (unit test) alongside T019 (the implementation it tests) +- T034 in Polish + +### Sequencing Note + +T020 (adding `requireRole('ADMIN')` across 002-009's existing routes) is the one task in this +feature that touches code outside `identity/*` — run each touched module's own existing test +suite immediately after, not only in T036's final regression pass, so a role-gating regression +in, say, 007's own suite is caught close to its cause rather than at the very end. + +--- + +## Implementation Strategy + +### MVP First (User Stories 1-2 Only) + +1. Setup + Foundational (T001-T006) +2. User Story 1 (T007-T013) → login works, no account-existence leak +3. User Story 2 (T014-T021) → the gate is real everywhere it already existed +4. **STOP and VALIDATE**: Quickstart Scenarios 1-2 pass, including the cross-module spot-check + (T016). This is the feature's MVP — every other user story is a smaller addition on top of a + now-real auth system. + +### Incremental Delivery + +1. Setup + Foundational → schema migrated, demo accounts have real passwords +2. Add User Story 1 → login is real +3. Add User Story 2 → the gate is real everywhere (P1-complete, MVP) +4. Add User Story 3 → self-identity, re-validated against live account state +5. Add User Story 4 → admins can provision new accounts +6. Add User Story 5 → explicit logout +7. Polish → full regression across every feature this touches