Files
support_backend/specs/010-identity-auth/tasks.md
T
saqib mirandClaude Sonnet 5 40687f68fa feat(010-identity-auth): real staff login, session verification, and role gating
Replaces the no-op fastify.authenticate stub and the never-implemented
identity/auth login with real bcrypt password verification, JWT session
issuance/verification (reusing the existing JWT_SECRET), and a Redis-backed
revocation denylist for logout. Adds requireRole('ADMIN') to admin-only
configuration writes across 002-009 that previously relied on a decorator
that never actually checked anything. Adds self-identity (GET /auth/me,
re-validated against live account state) and admin-provisioned accounts
(POST /admin/users).

Making the auth check genuinely reject invalid/missing tokens exposed that
~18 pre-existing integration test files called already-gated routes with no
Authorization header (safe against the old no-op stub, broken against a real
one) — fixed via a shared tests/helpers/auth.ts (loginAs/authHeader) and a
file-by-file pass, plus two related SLA-run cleanup races exposed once admin
setup calls in those files' own beforeAll blocks started actually succeeding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:45:37 +05:30

14 KiB

description
description
Task list for 010-identity-auth

Tasks: Identity and Authentication

Input: Design documents from specs/010-identity-auth/

Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/identity-auth-contract.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