verae-staff-iam/TOTP.md
George Lambert 566b29f4ac feat: GET /roles/catalog and /users/:id/roles for starter/ender/admin
Public catalog for dual-staff SoT when edge STAFF_IAM_URL is set.
2026-10-14 00:55:27 -04:00

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

  1. Leave policy off in pure local labs if seed passwords without apps are enough.
  2. For shared/staff hosts: STAFF_TOTP_REQUIRED=1, restart IAM, have each user sign in once and complete the gate.
  3. Confirm GET /health shows "totpRequired": true.
  4. Confirm GET /enroll/status (with session) shows totpEnrollmentRequired: false after enable.
  5. Keep mTLS at the proxy; do not expect staff-iam to mint device certs.
Variable Effect
STAFF_TOTP_REQUIRED=1 Force enroll before full access; block disable
STAFF_MTLS_ENFORCE=1 Orthogonal cert gate — ENROLL.md
  • ENROLL.md — mTLS states, nextSteps, proxyHeadersSeen, proxy headers only
  • README.md — install, usage, seed users