Skip to content

SCRUM-259: AdminUI account page

Plan ref: UI-14 (docs/11-admin-plane-plan.md). Needs the backend in SCRUM-259 (PR #77: /admin-auth/account/* and is_root/mfa_enabled on /admin-auth/me). Stacked on SCRUM-258 (UI).

What exists

Piece Notes
/account any signed-in user; linked from the sidebar footer and the ⌘K palette, not a nav group
Password current, new, confirm, strength meter; success toast says other sessions were signed out; invalid_credentials / validation_failed inline
Two-factor state read from /admin-auth/me on load (mfa_enabled, is_root). Root: "password-only" note. Not enabled: "Set up" is the only call to bearer mfa/enroll (it writes a pending secret), then QR, secret, code, recovery codes with the saved confirmation. Enabled: regenerate recovery codes (needs a current code) and, for non-admins only, turn off (needs a code, warns what it removes); admins see "required for admin accounts"
Sessions device summary parsed from the user agent, IP, signed in, last used, "This device" badge; "Sign out other sessions" confirms, toasts the count, reloads the list
Shared pieces QR, secret, recovery-codes and strength-meter extracted to src/components/auth/ and reused by the enrollment and onboarding views
Mock stateful account endpoints; /me reflects enroll/disable; admins refused on disable

How to verify

cd services/adminui
npm run typecheck && npm run lint && npm run format:check && npx vitest run && npm run build
npx playwright test
Mock sign-ins used by tests/e2e/account.spec.ts: viewer.changepw@example.com (change password, then sign in with the new one), liveops.revoke@example.com (sign out others), liveops.enroll@example.com (enroll; secret JBSWY3DPEHPK3PXP, code 123456), admin@example.com (no Turn off), root@example.com (password-only).

Results at time of writing

  • typecheck, lint, format:check, build: pass.
  • vitest: 492 passed (52 files), incl. "loading the page does not call enroll" and the root note.
  • Playwright: 44/44 passed (5 account flows).

How it was built

DeepSeek scoped (Landlock) to services/adminui, reasoning effort low: round 1 (723 s, ~73k output tokens) built the page; it read MFA state by calling enroll and reading the error, which writes a pending secret on every visit. Claude added is_root and mfa_enabled to /admin-auth/me on the backend branch; round 2 (452 s, ~24k output tokens) switched the page to it and removed the probe. Claude review: contract checked against account.go, one comment tidied.