# Auth, Revoke, and Repair Flows ## Public Auth Flow ```mermaid sequenceDiagram participant Client participant Gateway participant Auth participant Abuse as Resend throttle participant User as UserDirectory participant Mail as MailSender participant Challenge as ChallengeStore participant Session as SessionStore participant Config as ConfigProvider participant Projection as Gateway projection publisher Client->>Gateway: POST /api/v1/public/auth/send-email-code Gateway->>Auth: POST /api/v1/public/auth/send-email-code Auth->>Abuse: check and reserve cooldown alt throttled Abuse-->>Auth: throttled Auth->>Challenge: create delivery_throttled challenge Auth-->>Gateway: 200 {challenge_id} else allowed Abuse-->>Auth: allowed Auth->>User: ResolveByEmail(email) User-->>Auth: existing / creatable / blocked Auth->>Challenge: create pending challenge alt blocked Auth->>Challenge: mark delivery_suppressed else not blocked Auth->>Mail: SendLoginCode(email, code) Mail-->>Auth: sent / suppressed / failure Auth->>Challenge: persist final delivery outcome end Auth-->>Gateway: 200 {challenge_id} end Client->>Gateway: POST /api/v1/public/auth/confirm-email-code Gateway->>Auth: POST /api/v1/public/auth/confirm-email-code Auth->>Challenge: load and validate challenge Auth->>User: EnsureUserByEmail(email, registration_context) User-->>Auth: existing / created / blocked Auth->>Config: LoadSessionLimit() Auth->>Session: CountActiveByUserID(user_id) Auth->>Session: create device session Auth->>Challenge: CAS to confirmed_pending_expire Auth->>Session: reread current stored session view Auth->>Projection: publish gateway snapshot Auth-->>Gateway: 200 {device_session_id} ``` ## Revoke and Block Flow ```mermaid sequenceDiagram participant Caller as Trusted internal caller participant Auth participant User as UserDirectory participant Session as SessionStore participant Projection as Gateway projection publisher participant Gateway Caller->>Auth: revoke or block request alt block by user or email Auth->>User: apply block mutation User-->>Auth: blocked / already_blocked end Auth->>Session: revoke one or many sessions Session-->>Auth: updated source-of-truth sessions loop each affected session Auth->>Projection: publish revoked snapshot end Auth-->>Caller: 200 acknowledgement Projection-->>Gateway: revoked session snapshot ``` ## Projection Repair On Retry Projection writes happen after source-of-truth updates. If projection publish fails after state is already stored, the caller sees `service_unavailable`, and the repair path is to repeat the same request. ```mermaid sequenceDiagram participant Client participant Auth participant Challenge as ChallengeStore participant Session as SessionStore participant Projection as Gateway projection publisher Client->>Auth: confirm-email-code Auth->>Challenge: validate challenge Auth->>Session: create session Auth->>Challenge: persist confirmed_pending_expire Auth->>Projection: publish snapshot Projection-->>Auth: failure Auth-->>Client: 503 service_unavailable Client->>Auth: repeat same confirm-email-code Auth->>Challenge: load confirmed_pending_expire challenge Auth->>Session: load stored session from confirmation metadata Auth->>Projection: republish current stored session view Projection-->>Auth: success Auth-->>Client: 200 {device_session_id} ``` ## Confirm-Race Cleanup Concurrent identical confirms are allowed to race at the store level, but the service converges them back to one surviving active session. - the winning CAS stores challenge confirmation metadata and publishes the surviving session snapshot - a superseded session created by a losing racing request is revoked best-effort with `reason_code=confirm_race_repair` - cleanup uses the same projection helper, but cleanup failure is not part of the caller-visible success contract