Files
support_backend/specs/010-identity-auth/contracts/identity-auth-contract.md
T
saqib mirandClaude Sonnet 5 3b4c220a45 plan: design for identity and authentication feature (010)
Phase 0 research resolves the library choices (jsonwebtoken + bcryptjs,
chosen partly to avoid native-build friction on Windows dev
environments), the Redis-backed revocation-denylist shape (reusing
002's own jti-replay-protection pattern exactly), a 4-hour token
lifetime, and why fastify.authenticate populating the already-shared
reqContext.actorId/actorType retroactively makes every audit trail
since 007 accurate for real agent/admin actions instead of always
'unknown'.

Also surfaces and scopes a real gap found along the way: User (login
identity) and Agent (routing/skills profile) have never been linked.
Adds Agent.userId as a nullable FK now (cheap, additive) without
building the actual linking workflow, which belongs in 006's own
identity/agents admin screens as a later, separate piece of work.

Phase 1 adds data-model.md, the login/self-identity/account-creation/
logout contract, and five quickstart scenarios including a specific
requirement to re-verify at least one already-shipped admin route per
module (002-009), not just this feature's own new endpoints.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 11:14:42 +05:30

2.5 KiB

Contract: Identity and Authentication

Login

  • POST /auth/login — body { email, password }. 401 on any failure (wrong password, no such account, or a deactivated account) with an identical response body/status in every case (FR-002/SC-003) — never a distinguishable "no such user" vs "wrong password." 200 with { token, user: { id, email, name, role } } on success.

Self-identity

  • GET /auth/me — gated by fastify.authenticate. 401 if the token is missing/invalid/ expired/revoked. 401 if the account behind a structurally-valid token no longer exists or is deactivated (re-validated against current state, not the token's own claims alone). 200 with { id, email, name, role } on success.

Account creation (admin-only)

  • POST /admin/users — gated by fastify.authenticate + requireRole('ADMIN'). Body { email, name, role, password } (role one of ADMIN/AGENT). 403 for a valid non-admin session. 409 if email is already in use. 201 with the created { id, email, name, role } (never the password or its hash) on success.

Logout

  • POST /auth/logout — gated by fastify.authenticate. Revokes the calling token's own jti (Redis denylist, TTL = remaining lifetime) so it's rejected on any further use even before its natural expiry. 200 on success.

Guarantees (callable contract)

  1. Every route already gated by fastify.authenticate across 002-009 continues to accept a valid session and now genuinely rejects a missing/invalid/expired/revoked one — the gate itself changes from a no-op to a real check; which routes carry the gate is unchanged (FR-006, SC-001).
  2. A route additionally gated by requireRole('ADMIN') rejects a structurally valid session whose role isn't ADMIN, with a response distinguishable from "no valid session at all" (403 vs 401) (FR-005, SC-002).
  3. A login failure never reveals whether the submitted email corresponds to an existing account — verified by comparing the exact response for a wrong password against a wholly nonexistent email (FR-002, SC-003).
  4. No password is ever stored, logged, or returned anywhere in plaintext — only passwordHash is persisted, and no response body (login, self-identity, account creation) ever includes it (FR-003, SC-004).
  5. An admin-created account can log in immediately with the password it was created with, no manual step in between (FR-008, SC-005).
  6. A token revoked via logout is rejected on any further use, even before its natural expiry (FR-009).