Documentation Index
Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
Use this file to discover all available pages before exploring further.
Why this exists
MFA enforcement protects against credential-leak risk on admin accounts. Even if an attacker steals a session cookie or API password, they cannot mutate decisioning configuration (offers, contact policies, decision flows, MCP playbooks, models, approvals, etc.) without the second factor. The enforcement runs in edge middleware for low latency — typically <5ms additional overhead per request — and applies to every state-changing HTTP method (POST / PUT / PATCH / DELETE) on /api/* routes.
How the flow works
- Admin signs in with email + password (or Google OAuth).
-
If the user has MFA enabled, NextAuth issues a JWT with
mfaPending = trueandmfaVerifiedAt = null. -
Admin attempts a state-changing API call. Middleware sees
mfaPending && !fresh(mfaVerifiedAt)and returns403 MFA_REQUIREDwith this body: -
Client calls
POST /api/v1/auth/mfawith{ action: "verify", token: "<6-digit TOTP>" }. Returns200 { verified: true }on success or400on bad token. -
Client calls
useSession().update({ mfaVerifiedAt: new Date().toISOString() })to refresh the JWT — theauth.tsJWT callback handles thetrigger="update"path and writes the timestamp. - Subsequent admin writes pass for 15 minutes, after which the user must re-verify.
Step-up TTL
Default is 15 minutes. The TTL is intentionally short to limit blast radius if a session is hijacked. Sliding-window: every successful verify resets the 15-minute timer. The TTL is hardcoded inplatform/src/middleware.ts as
MFA_STEPUP_TTL_MS = 15 * 60 * 1000. To change it, modify the constant
and redeploy. There is no per-tenant configuration today.
What’s bypassed
GET/HEAD/OPTIONSrequests — no enforcement (read-only)- Non-admin users — no enforcement (the gate is admin-only)
- Users with MFA not enabled — no enforcement (
mfaPendingisfalse) /api/v1/auth/mfaitself — must be reachable to do the verify/api/auth/*(NextAuth handlers) — needed for sign-in flow- API-key-authenticated server-to-server calls — these don’t carry a JWT and are gated separately by API-key scope
Kill switch (incident only)
SetMFA_ENFORCEMENT_DISABLED=true in the deployment environment to
bypass enforcement for all requests. This is only for incident
recovery — for example, if the TOTP server’s clock is drifting and
legitimate codes are being rejected.
When set, requests that would have been blocked are passed through with
no logging change. Set the env var back to false (or unset) and redeploy
to re-enable.
Operator runbook
| Symptom | Likely cause | Action |
|---|---|---|
| All admin writes returning 403 MFA_REQUIRED | Admin has MFA enabled but never completed verify | Have admin do POST /api/v1/auth/mfa { action: "verify" } then update session |
| Admin verified successfully but next write returns 403 | JWT didn’t receive the timestamp from useSession().update() | Check client-side: useSession().update({ mfaVerifiedAt: new Date().toISOString() }) must be called after the verify response |
| 403 returns even though admin verified < 15 min ago | Clock drift between auth server and middleware host | Check NTP on both hosts. Or temporarily disable via kill switch |
| All non-admin users returning 403 | Bug — non-admins shouldn’t be gated | File issue immediately. Set kill switch as workaround |
| Production-grade incident: locked out of admin | Kill switch not yet set, need access NOW | Set MFA_ENFORCEMENT_DISABLED=true in env, redeploy. Re-enable once admin can verify |
What’s in scope (W2.2 baseline)
- ✅ Edge middleware enforcement on all
POST/PUT/PATCH/DELETEto/api/* - ✅
mfaVerifiedAtJWT timestamp + 15-minute sliding window - ✅
MFA_ENFORCEMENT_DISABLEDenv kill switch - ✅ Source-level regression tests at
platform/src/__tests__/middleware-mfa.test.ts - ✅ MFA verify endpoint already existed at
POST /api/v1/auth/mfa
Out of scope (later)
- Per-tenant TTL configuration
- WebAuthn / Passkey support (currently only TOTP + backup codes)
- Audit log entry on every verify (currently logged at info via
logger) - IP-based step-up (require fresh verify when source IP changes mid-session)
Source files
platform/src/middleware.ts— enforcementplatform/src/lib/auth.ts— JWT callback writesmfaVerifiedAtontrigger="update"platform/src/app/api/v1/auth/mfa/route.ts— verify endpoint (TOTP validation, backup-code support, rate limiting)platform/src/__tests__/middleware-mfa.test.ts— regression test