5.2 KiB
5.2 KiB
Staff authenticator (TOTP)
Optional per-user TOTP (RFC 6238, 6 digits / 30s). Orthogonal to device-cert mTLS — mTLS is proxy headers only and never mints PKI here (ENROLL.md).
Staff sessions use cookie / Bearer staff_session only. This service never issues customer xVerae- tokens.
Default (policy off)
| Step | API | Result |
|---|---|---|
| Setup | POST /2fa/setup (signed in) |
Returns secret, uri (otpauth://…), optional qr data-URL. Stores pending secret; totpEnabled stays false until enable. |
| Enable | POST /2fa/enable { "code": "123456" } |
Verifies current code; sets totpEnabled: true. |
| Login | POST /login with password |
If totpEnabled, requires totpCode or returns 401 { "error": "totp_required" }. |
| Disable | POST /2fa/disable { "code" } |
Clears secret when policy is off. |
Seed lab users ship with totpEnabled: false.
STAFF_TOTP_REQUIRED enroll path
Set STAFF_TOTP_REQUIRED=1 (or true) so every staff account must enroll authenticator before full app access.
password OK
│
▼
POST /login ──► session issued (staff_session cookie / token)
│ body includes:
│ totpRequired: true
│ totpEnrollmentRequired: true (until enable)
│ user.totpEnrollmentRequired: true
▼
full APIs / doors ──► 403 { error: "totp_enrollment_required", … }
│
│ still allowed while gated:
│ GET /me, /enroll/status, /health, /roles, /roles/catalog
│ POST /2fa/setup, /2fa/enable, /logout
▼
POST /2fa/setup ──► secret + otpauth URI (+ QR if qrcode dep present)
▼
POST /2fa/enable { code } ──► totpEnabled: true
│ totpEnrollmentRequired: false
▼
full access (roles, directory, /check, other doors)
Policy rules
| Rule | Behavior |
|---|---|
| Seeds | Stay totpEnabled: false until each user enrolls |
| Login | Password (and TOTP if already enabled) still succeed; session is real |
| Gate | Any path outside the allow-list returns 403 totp_enrollment_required |
| Disable | POST /2fa/disable → 403 { error: "totp_required_by_policy" } while env is on |
| mTLS | Unchanged; cert gate is separate (STAFF_MTLS_ENFORCE + mtlsEnrolled) |
Allow-list while enrollment required
GET /me
GET /enroll/status
GET /health
GET /roles
GET /roles/catalog
POST /2fa/setup
POST /2fa/enable
POST /logout
Public (no session) role surfaces used by edge dual-staff:
GET /roles/catalog → { roles: ["starter","ender","admin"] }
GET /users/:idOrUsername/roles → { id, username, active, roles[] } (no secrets)
HTML static doors and the in-app totp-gate modal use setup/enable only until unlock.
/enroll/status TOTP fields
Same endpoint as mTLS status. TOTP flags ride alongside device-cert fields:
| Field | Meaning |
|---|---|
updatedAt |
ISO-8601 generation time of this response |
state / nextSteps / proxyHeadersSeen |
mTLS surface only — ENROLL.md |
totpEnabled |
Signed-in user has 2FA on |
totpRequired |
STAFF_TOTP_REQUIRED is on |
totpEnrollmentRequired |
Policy on and this user still must enroll |
No cert or TOTP secrets appear in /enroll/status. No fake PKI.
How the login UI uses these
| Surface | Behavior |
|---|---|
public/login.html |
Hides TOTP field until needed. On 401 totp_required or ?error=totp, shows the 6-digit input and resubmits totpCode with password. After success, redirects to next (app may then show enroll gate). Also paints mTLS banner from /enroll/status (state, updatedAt, proxyHeadersSeen chips, nextSteps) — copy only. |
| Login JSON | { token, user, totpRequired, totpEnrollmentRequired } — UI reads flags on user too via /me. |
public/app.html |
If user.totpEnrollmentRequired, full-screen totp-gate: auto POST /2fa/setup, QR/secret, enable form. Directory tabs stay blocked until enable. My security tab: setup/disable (disable blocked when policy on) + device-cert panel from /enroll/status. |
public/enroll-2fa.html |
First-login optional path: setup → enable → continue (or skip when policy off). |
| Errors | totp_required = code missing/wrong at login · totp_enrollment_required = must finish /2fa/* · totp_required_by_policy = cannot disable |
Operator checklist
- Leave policy off in pure local labs if seed passwords without apps are enough.
- For shared/staff hosts:
STAFF_TOTP_REQUIRED=1, restart IAM, have each user sign in once and complete the gate. - Confirm
GET /healthshows"totpRequired": true. - Confirm
GET /enroll/status(with session) showstotpEnrollmentRequired: falseafter enable. - Keep mTLS at the proxy; do not expect staff-iam to mint device certs.
Related env
| Variable | Effect |
|---|---|
STAFF_TOTP_REQUIRED=1 |
Force enroll before full access; block disable |
STAFF_MTLS_ENFORCE=1 |
Orthogonal cert gate — ENROLL.md |