feat(ui): add two-factor authentication (TOTP) support#5888
feat(ui): add two-factor authentication (TOTP) support#5888luannmoreira wants to merge 3 commits intomasterfrom
Conversation
|
Claude finished @luannmoreira's task in 9m 15s —— View job Code Review CompleteReviewed 18 files across code quality, security, testing, TypeScript/React patterns, and architecture. 14 inline issues posted (2 critical, 4 high, 7 medium, 1 low). Additional notesMFA endpoints are cloud/enterprise-only: All No test coverage for any new MFA code: The PR adds ~2100 lines of new code with zero tests. The existing
Code duplication: OTP input handling (state, validation, auto-advance, backspace) is duplicated across 3 components. Footer links are duplicated across 3 pages. Download/copy utility logic is duplicated across 2 modals. Consider extracting shared
If you've addressed the feedback and want a new review, tag |
9c2f663 to
2be77c1
Compare
ui-react/apps/admin/src/components/mfa/MfaRecoveryCodesModal.tsx
Outdated
Show resolved
Hide resolved
ui-react/apps/admin/src/components/mfa/MfaRecoveryCodesModal.tsx
Outdated
Show resolved
Hide resolved
23f5243 to
b81ea57
Compare
|
/review |
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success alongside secret/recoveryCodes - Use strict equality for login endpoint check in interceptors (=== "/api/login") - Remove FormEvent from MfaEnableDrawer button handlers (not form submits) - Add aria-label to OTP inputs in MfaResetComplete for screen reader support Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
20e4bc6 to
8c4106a
Compare
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success alongside secret/recoveryCodes - Use strict equality for login endpoint check in interceptors (=== "/api/login") - Remove FormEvent from MfaEnableDrawer button handlers (not form submits) - Add aria-label to OTP inputs in MfaResetComplete for screen reader support
8c4106a to
58acdae
Compare
|
/review |
|
/review |
|
Claude finished @otavio's task in 7m 21s —— View job Review: Round 6
SummaryFound 5 new issues across the MFA implementation:
No issues found by: Security review, Architecture/API contract review 🤖 Generated with Claude Code |
58acdae to
ee17601
Compare
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; partial paste fills available digits instead of silently discarding; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success - Use strict equality for login endpoint check in interceptors - Remove FormEvent from MfaEnableDrawer button handlers - Add aria-label to OTP inputs in MfaResetComplete - Navigate to /dashboard when backend omits X-Expires-At header - Show error feedback when disable MFA fails in recovery window - Reset MfaDisableDialog state on reopen to avoid stale inputs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; partial paste fills available digits instead of silently discarding; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success - Use strict equality for login endpoint check in interceptors - Remove FormEvent from MfaEnableDrawer button handlers - Add aria-label to OTP inputs in MfaResetComplete - Navigate to /dashboard when backend omits X-Expires-At header - Show error feedback when disable MFA fails in recovery window - Reset MfaDisableDialog state on reopen to avoid stale inputs
ee17601 to
7955b3e
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 9m 39s —— View job Review: Round 7
SummaryReviewed 33 files across code quality, security, testing, language patterns, and architecture (including cross-repo validation against Validated and discarded:
Previously reported issues still open (not re-reported inline):
If you've addressed the feedback and want a new review, tag 🤖 Generated with Claude Code |
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; partial paste fills available digits instead of silently discarding; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success - Use strict equality for login endpoint check in interceptors - Remove FormEvent from MfaEnableDrawer button handlers - Add aria-label to OTP inputs in MfaResetComplete - Navigate to /dashboard when backend omits X-Expires-At header - Show error feedback when disable MFA fails in recovery window - Reset MfaDisableDialog state on reopen to avoid stale inputs
7955b3e to
3243558
Compare
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; partial paste fills available digits instead of silently discarding; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success - Use strict equality for login endpoint check in interceptors - Remove FormEvent from MfaEnableDrawer button handlers - Add aria-label to OTP inputs in MfaResetComplete - Navigate to /dashboard when backend omits X-Expires-At header - Show error feedback when disable MFA fails in recovery window - Reset MfaDisableDialog state on reopen to avoid stale inputs
3243558 to
73ee7c6
Compare
- Add complete MFA flow with TOTP verification - Add MFA recovery codes and email-based reset flow - Add MFA login, recovery, and reset pages - Fix recovery code reuse bug in MfaRecover - Fix recovery codes modal to prevent generating invalid codes - Add comprehensive error handling and accessibility improvements - Optimize OTP input with useCallback - Add ARIA labels and keyboard navigation support - Add security enhancements (autocomplete=off, QRCode validation) - Document TOTP secret trust boundary security tradeoff - Fix import organization and remove duplicate types
- MfaEnableDrawer: step flow, recovery email, QR/OTP verification, sensitive data cleanup (secret, recoveryCodes, qrLink) - MfaDisableDialog: TOTP/recovery code modes, email-reset flow - MfaRecoveryTimeoutModal: countdown display, disable action, close behavior - MfaLogin: OTP input, submit, countdown/timeout flow - MfaRecover: recovery code submission, timeout modal integration - useOtpInput: input handling, paste, keyboard nav, completion state - useCountdown: countdown logic, expiry detection - authStore: login/logout flows, MFA token handling, partialize exclusions - interceptors: MFA token storage scoped to /api/login only - Fix auto-cleanup (afterEach cleanup) in test setup for jsdom environment
- Rename MFA routes: /mfa-login → /login-mfa, /mfa-recover → /recover-mfa, /mfa-reset-request → /reset-request-mfa for consistent action-mfa pattern - Remove MfaRecoveryCodesModal (never rendered; regeneration not supported) - Remove MfaResetVerify page (replaced by MfaResetComplete flow) - Replace useShallow object selectors with individual per-field selectors in MfaRecover, MfaResetRequest, MfaDisableDialog, and Profile - Fix stale closure in MfaRecoveryTimeoutModal: capture onClose in ref - Memoize getValue/isComplete in useOtpInput; partial paste fills available digits instead of silently discarding; add try-finally in useRecoveryCodeActions for reliable blob URL cleanup - Clear mfaToken/pendingMfaUser/mfaRecoveryExpiry in setCompleteSession - Clear qrLink immediately after MFA enable success - Use strict equality for login endpoint check in interceptors - Remove FormEvent from MfaEnableDrawer button handlers - Add aria-label to OTP inputs in MfaResetComplete - Navigate to /dashboard when backend omits X-Expires-At header - Show error feedback when disable MFA fails in recovery window - Reset MfaDisableDialog state on reopen to avoid stale inputs
73ee7c6 to
a62562c
Compare

Summary
Adds two-factor authentication (TOTP) support to the admin UI, allowing users to secure their accounts with
authenticator apps like Google Authenticator, Authy, or 1Password.
Key Features:
Implementation Details:
x-mfa-tokenheader) without page reloadsDependencies Added:
qrcode.react- QR code generation for authenticator app setupotpauth- TOTP URI formatting (otpauth://totp/...)Closes https://github.com/shellhub-io/team/issues/62