Contract-freeze · UX approval

Account security — Two-factor authentication · Approval Frames

These frames demonstrate the MFA management experience before any feature UI is implemented: enrolling an authenticator app (TOTP), adding a phone for text-message codes, one-time recovery codes, removing a method, and the step-up challenge shown mid sign-in. Approving them freezes the visual + interaction contract the implementation and frontend-test-engineer's Playwright checks are verified against. Built in the fuse-seam design system (tokens only). No provider name appears anywhere in the UI.

Reached from the Account security hub (sibling feature) → Two-factor authentication. Route base /account/security/mfa. The sign-in step-up lives on the login path /login/mfa.

Walk the flow

Frame 01 · /account/security/mfa

Two-factor overview

Enrolled methods list, add-a-method menu, recovery-codes status. Authenticator is available; text-message codes are offered only when the channel can deliver.

populatedempty (no MFA)loadingload error
Frame 02 · …/mfa/add/authenticator

Add an authenticator app

Scan the QR (or copy the setup key), then confirm with a 6-digit code. Fail-closed on a wrong or expired code.

scan + verifyinvalid codeexpired
Frame 03 · …/mfa/add/phone

Add a phone (text message)

Enter a phone, receive a code, confirm. Honest degrade: when text messages can't be delivered the option is disabled with a reason — never offered as if it works.

enter + confirmchannel unavailableinvalid code
Frame 04 · …/mfa/recovery-codes

Recovery codes

Shown once, never retrievable again. Copy/download, then regenerate (which invalidates the old set).

shown onceregenerate confirm
Frame 05 · /login/mfa

Step-up challenge (mid sign-in)

Login returned mfa_required. Pick a factor, enter the code, complete sign-in. Fail-closed on bad/expired code; delivery failure is surfaced.

TOTP promptchoose factorinvalid / expired
Frame 06 · /account/security/mfa

Remove a method

Confirm removal. Fail-closed: removing your only remaining factor while two-factor is required is blocked (409) — you can't lock yourself out.

confirmremove-last blocked (409)

Build inventory — what approving these frames approves

Flows
MfaOverviewFlow · /account/security/mfa TotpEnrollFlow · /account/security/mfa/add/authenticator SmsEnrollFlow · /account/security/mfa/add/phone RecoveryCodesFlow · /account/security/mfa/recovery-codes MfaStepUpFlow · /login/mfa
Components
MfaOverviewPanelMfaFactorListMfaFactorRow AddFactorMenuTotpEnrollPanelQrProvisioning OtpInputSmsEnrollPanelChannelUnavailableNotice RecoveryCodesPanelRemoveFactorDialogMfaChallengeCard FactorPicker
Packages
@fuzefront/account-security-ui @fuzefront/security-client (existing)
Missing DS primitive
OtpInput — a segmented one-time-code field is not yet in @fuzefront/design-system. Named here for frontend-engineer to add as a foundation primitive; not styled one-off.
● Awaiting approval

Design system: @fuzefront/design-system (fuse-seam) · Contract: packages/security/openapi.yaml@fuzefront/security-client · Package: @fuzefront/account-security-ui · Flag: fuzefront.security.mfa-management (default OFF).
Honest-degrade signal: GET /v1/security/methodsmfa.types / verification.sms gate whether text-message codes are offered. Per-flow approval lives in manifest.json; approve by setting a flow's approved: true (+ approver + date) or reply @claude approve / @claude reject: <reason>.

Review · mfa-management approve or reject each flow ↓
mfa-overviewpendingApproveReject
totp-enrollpendingApproveReject
sms-enrollpendingApproveReject
recovery-codespendingApproveReject
mfa-step-uppendingApproveReject