docs: PR body for feature/staff-role-switcher

Add docs/PR-staff-role-switcher.md covering role interface, TOTP/enroll,
and links to ENROLL, TOTP, and PKI READY packs (PROD-CA cutover, AuthZ
enroll design, CRL/OCSP deploy). Link from README Docs table.
This commit is contained in:
George Lambert 2026-10-14 04:34:25 -04:00
commit 5922dc704b
2 changed files with 96 additions and 0 deletions

View file

@ -61,6 +61,7 @@ Open `http://127.0.0.1:3028/login`. Persist store with `STAFF_IAM_PATH`. Login i
| [TOTP.md](./TOTP.md) | Authenticator 2FA + **`STAFF_TOTP_REQUIRED`** enroll path; how login / app UI use flags |
| [NATS.md](./NATS.md) | NATS notes (if used) |
| [SUMMARY.md](./SUMMARY.md) | One-line service summary |
| [docs/PR-staff-role-switcher.md](./docs/PR-staff-role-switcher.md) | PR body for `feature/staff-role-switcher` (IAM + UI companion) |
## Seed lab users

View file

@ -0,0 +1,95 @@
# PR: feature/staff-role-switcher (verae-staff-iam)
**Repo:** `verae-staff-iam`
**Branch:** `feature/staff-role-switcher` → `main`
**Forgejo:** https://git.georgelambert.org/marchon/verae-staff-iam
**Companion PR:** `verae-staff-ui` same branch (`docs/PR-staff-role-switcher.md`)
Paste this body (or link this file) when opening the Forgejo pull request.
## Summary
Staff IAM gains an **active role interface** (`currentRole` on session), product **roles catalog** for edge dual-staff, first-login **mustChangePassword** + optional/required **TOTP**, and an honest **device-cert enroll** surface: default `POST /enroll/csr` → **501**, opt-in **LAB ONLY** mint via `LAB_CA_ENABLE=1` + `lab-test-ca.sh` (refused when `NODE_ENV=production`). Production CA mint is **not** shipped; ops kits are **READY packages** only.
## Why
- Multi-role staff need one signed-in session and a switchable interface (CS / sales / operator / …) without re-login.
- Edge dual-staff impersonate needs a public starter/ender/admin catalog and per-user roles lookup.
- Security gates (password change, TOTP, mTLS status) must be real in software while production PKI stays external.
## What landed (vs `main`)
### Role interface switcher
- Session store carries `currentRole`; `GET /me`, `GET /interfaces`, `POST /session/role`
- `GET /roles/catalog` → `{ roles: ["starter","ender","admin"] }` (public)
- `GET /users/:idOrUsername/roles` → roles-only (no secrets); unknown/inactive → 404
- App UI: Interface dropdown when capabilities ≥ 2; People & roles for admins
### First login + TOTP
- New accounts: `mustChangePassword=true`; doors gated `403 must_change_password` until `POST /password/change`
- Optional TOTP (`/2fa/setup`, `/2fa/enable`); `STAFF_TOTP_REQUIRED=1` forces enroll path
- `POST /2fa/skip` denied under required policy; `POST /admin/mark-must-change` for invite re-mark
- Login UI + My security / enroll-2fa / change-password pages (2026 brand kit)
### Enroll / PKI (no production mint)
- `GET /enroll/status` — state, nextSteps, `csrSchemaUrl`, `csrPostStatus`, `labCa`, `proxyHeadersSeen`
- `GET /enroll/csr/schema` — fields-only (`mint: false`)
- `POST /enroll/csr` — PEM shape gate (**400** invalid); default **501** `not_implemented`; lab **201** + `X-Lab-CA: 1` only when lab ready and non-prod
- `scripts/lab-test-ca.sh` — `init` / `sign` / `crl` / `warn` (LAB ONLY)
- Audit `csr_attempt` without storing PEM
### Docs
- Operator: [ENROLL.md](../ENROLL.md), [TOTP.md](../TOTP.md)
- Refuse matrix: [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md)
- Design: [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md), [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md), [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md)
- **PKI READY packs** (ops kits; no mint code): see below
## Link: ENROLL · TOTP · PKI READY packs
| Pack | Path | Role |
|------|------|------|
| **ENROLL** | [ENROLL.md](../ENROLL.md) | Device cert mTLS: status, CSR schema, default 501, lab 201 contract |
| **TOTP** | [TOTP.md](../TOTP.md) | Authenticator 2FA + `STAFF_TOTP_REQUIRED` enroll path |
| **PROD-CA cutover (READY)** | [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) | Production CA mint/renew/revoke cutover (external CA; IAM does not become CA) |
| **AuthZ enroll (READY)** | [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) | Design before dropping default 501 for a real AuthZ mint path |
| **CRL/OCSP deploy (READY)** | [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) | Production CRL/OCSP proxy/CA deploy checklist |
| Refuse matrix | [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md) | Lab-only 201 vs production 501; prod never lab |
| Backlog | [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) | What live production mint still requires |
**Invariants held (do not merge if broken):**
- Default `POST /enroll/csr` → **501**
- `LAB_CA_ENABLE` default **off**
- `NODE_ENV=production` **refuses** lab mint even if flag set
- No PKCS#12 / private key return; no demo “real” staff cert from IAM
## Test plan
```sh
cd verae-staff-iam
npm install
npm test
# expect: 19/19 pass (health PKI/lab matrix, password-totp, roles/currentRole)
```
Manual smoke (optional lab):
```sh
npm start # :3028
# login admin / admin-dev-key → change-password if marked → Interface dropdown if multi-role
# GET /roles/catalog ; GET /enroll/status ; POST /enroll/csr → 501
# LAB ONLY: ./scripts/lab-test-ca.sh init --dir ./lab-pki && LAB_CA_ENABLE=1 npm start
```
## Merge notes
- Merge **with** `verae-staff-ui` `feature/staff-role-switcher` (shared chrome / review template brand).
- Edge dual-staff: set `STAFF_IAM_URL` and use catalog + `/users/:id/roles` (see edge `IMPERSONATE-IAM-ROLES.md` when present).
- Production PKI cutover is **human/ops** via READY packs — not enabled by this merge.
- Seed passwords are lab-only; rotate before any shared environment.
## Out of scope (explicit)
- Live production device-cert mint/renew/revoke inside staff-iam
- Dropping default 501 without a real Signer + AuthZ path
- Production CRL/OCSP served from this process
- Customer portal accounts / `xVerae-` token issue (staff_session only)