# 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).