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>
4.0 KiB
4.0 KiB
Specification Quality Checklist: Identity and Authentication
Purpose: Validate specification completeness and quality before proceeding to planning Created: 2026-09-07 Feature: spec.md
Content Quality
- No implementation details (languages, frameworks, APIs)
- Focused on user value and business needs
- Written for non-technical stakeholders
- All mandatory sections completed
Requirement Completeness
- No [NEEDS CLARIFICATION] markers remain
- Requirements are testable and unambiguous
- Success criteria are measurable
- Success criteria are technology-agnostic (no implementation details)
- All acceptance scenarios are defined
- Edge cases are identified
- Scope is clearly bounded
- Dependencies and assumptions identified
Feature Readiness
- All functional requirements have clear acceptance criteria
- User scenarios cover primary flows
- Feature meets measurable outcomes defined in Success Criteria
- No implementation details leak into specification
Notes
- This feature was not on the original 11-phase roadmap — it surfaced as a genuine blocking gap
while planning supporthub-web's
001-agent-admin-ui:fastify.authenticatehas been a complete no-op stub since 002, andidentity/auth's login endpoint has never taken a password. Numbered 010 in supporthub-api's own sequence since it's a real, immediately-needed backend prerequisite, not deferred hardening. User/UserRole(with two seeded-but-passwordless demo accounts,admin@supporthub.internal/agent@supporthub.internal) and theAuthUser/JwtPayloadtypes insrc/common/typeswere all found already scaffolded, unwired, and clearly intended for exactly this feature since the original pre-speckit scaffold — this is a "finish the originally-intended wiring" feature, not a new design invented from nothing.- Scope is deliberately narrow: real login + real route gating + role checks + a self-identity endpoint + admin-created accounts + logout. Password reset, MFA, rate-limiting, and registration are explicitly out of scope (Assumptions), matching Phase 11's own "security hardening pass" as the more appropriate later home for those.
- All items pass; no revision iterations were needed.
- Implementation-time finding: making
fastify.authenticategenuinely reject invalid/missing tokens (FR-004) had a far larger blast radius than this feature's own tasks.md anticipated. Dozens of routes across features 002-009 were already declared withfastify.authenticateas a preHandler — safe to write against a no-op stub, but every one of those pre-existing integration tests had been calling them with noAuthorizationheader. Making the check real broke ~18 integration test files suite-wide, requiring atests/helpers/auth.ts(loginAs/authHeader) and a file-by-file pass adding real bearer tokens, well beyond the mechanicalrequireRole('ADMIN')rollout research.md had scoped for. A related, recurring bug: several files already declared a localconst token = issueIntegrationToken(...)for the unrelated 002 customer-trust-boundary flow, and naming the new admin/agent token variabletokenin the same scope produced aReferenceError: Cannot access 'token' before initialization— a genuine temporal-dead-zone collision, not a tooling bug — fixed by using a non-colliding name (authToken/adminToken/agentToken) per file. - A second, subtler implementation-time finding: once admin-setup calls in test
beforeAllblocks started actually succeeding (previously they silently 401'd against the no-op stub), wildcard/global SLA policies created by one integration test file could genuinely match tickets created by another file running against the same shared throwaway Postgres, leaving orphanedsla_runrows that RESTRICT-violated the FK on cleanup. Fixed by widening the affected files'afterAllcleanup to deletesla_runrows byticketIdand bypolicyId, not just one or the other.