import { CheckCircle, Security, Smartphone } from '@mui/icons-material' import { Alert, Box, Button, Card, CircularProgress, Divider, Input, Modal, ModalClose, ModalDialog, Stack, Typography, } from '@mui/joy' import QRCode from 'qrcode' import { useEffect, useState } from 'react' import { ConfirmMFA, DisableMFA, GetMFAStatus, RegenerateBackupCodes, SetupMFA, } from '../../utils/Fetcher' const MFASettings = () => { const [mfaEnabled, setMfaEnabled] = useState(false) const [loading, setLoading] = useState(true) const [setupModalOpen, setSetupModalOpen] = useState(false) const [disableModalOpen, setDisableModalOpen] = useState(false) const [backupCodesModalOpen, setBackupCodesModalOpen] = useState(false) const [setupData, setSetupData] = useState(null) const [qrCodeDataUrl, setQrCodeDataUrl] = useState('') const [verificationCode, setVerificationCode] = useState('') const [disableCode, setDisableCode] = useState('') const [backupCodes, setBackupCodes] = useState([]) const [setupStep, setSetupStep] = useState(1) // 1: QR Code, 2: Verification, 3: Backup Codes const [error, setError] = useState('') const [success, setSuccess] = useState('') useEffect(() => { fetchMFAStatus() }, []) const fetchMFAStatus = async () => { try { setLoading(true) const response = await GetMFAStatus() if (response.ok) { const data = await response.json() setMfaEnabled(data.mfaEnabled) } } catch (error) { console.error('Error fetching MFA status:', error) } finally { setLoading(false) } } const generateQRCode = async url => { try { const qrCodeDataUrl = await QRCode.toDataURL(url, { width: 200, margin: 2, }) setQrCodeDataUrl(qrCodeDataUrl) } catch (error) { console.error('Error generating QR code:', error) setError('Failed to generate QR code') } } const handleSetupMFA = async () => { try { setError('') const response = await SetupMFA() console.log('MFA Setup Response Status:', response.status) console.log('MFA Setup Response:', response) if (response.ok) { const data = await response.json() console.log('MFA Setup Data:', data) // Check for either qrCode (base64 image) or qrCodeUrl (TOTP URL) if (!data.qrCodeUrl || !data.backupCodes || !data.secret) { console.error('Missing required MFA data:', { hasQrCode: !!data.qrCode, hasQrCodeUrl: !!data.qrCodeUrl, hasSecret: !!data.secret, }) setError('Invalid response from server. Missing QR code or secret.') return } if (data.backupCodes) { console.log('Backup Codes:', data.backupCodes) setBackupCodes(data.backupCodes) } // If we have a qrCodeUrl, generate the QR code image if (data.qrCodeUrl) { await generateQRCode(data.qrCodeUrl) } setSetupData(data) setSetupModalOpen(true) setSetupStep(1) } else { // Handle different error status codes if (response.status === 404) { setError( 'MFA setup endpoint not found. This feature may not be available yet.', ) } else if (response.status === 401) { setError('Unauthorized. Please login again.') } else if (response.status === 500) { setError('Server error. Please try again later.') } else { const errorData = await response.json().catch(() => ({})) setError( errorData.message || `Failed to setup MFA (${response.status}). Please try again.`, ) } } } catch (error) { console.error('Error setting up MFA:', error) setError('Network error. Please check your connection and try again.') } } const handleConfirmMFA = async () => { try { setError('') const response = await ConfirmMFA( setupData.secret, verificationCode, setupData.backupCodes, ) if (response.ok) { setSetupStep(3) setMfaEnabled(true) setSuccess('MFA has been successfully enabled!') } else { setError('Invalid verification code. Please try again.') } } catch (error) { setError('Failed to confirm MFA. Please try again.') console.error('Error confirming MFA:', error) } } const handleDisableMFA = async () => { try { setError('') const response = await DisableMFA(disableCode) if (response.ok) { setMfaEnabled(false) setDisableModalOpen(false) setDisableCode('') setSuccess('MFA has been disabled successfully!') } else { setError('Invalid verification code. Please try again.') } } catch (error) { setError('Failed to disable MFA. Please try again.') console.error('Error disabling MFA:', error) } } const handleRegenerateBackupCodes = async () => { try { setError('') const response = await RegenerateBackupCodes() if (response.ok) { const data = await response.json() setBackupCodes(data.backupCodes) setBackupCodesModalOpen(true) setSuccess('New backup codes have been generated!') } else { setError('Failed to regenerate backup codes. Please try again.') } } catch (error) { setError('Failed to regenerate backup codes. Please try again.') console.error('Error regenerating backup codes:', error) } } const closeSetupModal = () => { setSetupModalOpen(false) setSetupStep(1) setVerificationCode('') setSetupData(null) setQrCodeDataUrl('') setError('') } const closeDisableModal = () => { setDisableModalOpen(false) setDisableCode('') setError('') } if (loading) { return ( ) } return (
Multi-Factor Authentication Add an extra layer of security to your account with multi-factor authentication (MFA). When enabled, you'll need to provide a verification code from your authenticator app in addition to your password when signing in. {success && ( setSuccess('')}> {success} )} {error && ( setError('')}> {error} )} Two-Factor Authentication {mfaEnabled ? 'Your account is protected with 2FA' : 'Secure your account with an authenticator app'} {mfaEnabled ? ( ) : ( )} {/* {mfaEnabled && ( Backup Codes Generate new backup codes in case you lose access to your authenticator )} */} {/* Setup MFA Modal */} Set up Multi-Factor Authentication {setupStep === 1 && setupData && ( Step 1: Scan the QR code below with your authenticator app (Google Authenticator, Authy, etc.) {qrCodeDataUrl || setupData.qrCode ? ( MFA QR Code ) : ( QR code could not be generated. Please try again or use the manual entry key below. )} Manual entry key: {setupData.secret} )} {setupStep === 2 && ( Step 2: Enter the 6-digit verification code from your authenticator app { if (e.key === 'Enter' && verificationCode.length === 6) { handleConfirmMFA() } }} onChange={e => setVerificationCode(e.target.value)} sx={{ textAlign: 'center', fontSize: '1.2em', letterSpacing: verificationCode.length === 0 ? '' : '0.4em', }} slotProps={{ input: { maxLength: 6, pattern: '[0-9]*', }, }} /> {error && {error}} )} {setupStep === 3 && ( MFA Successfully Enabled! Save these backup codes in a safe place You can use these codes to access your account if you lose your authenticator device. Each code can only be used once. {backupCodes?.map((code, index) => ( {code} ))} )} {/* Disable MFA Modal */} Disable Multi-Factor Authentication Disabling MFA will make your account less secure. Are you sure you want to continue? Enter a verification code from your authenticator app to confirm: { if (e.key === 'Enter' && disableCode.length === 6) { handleDisableMFA() } }} onChange={e => setDisableCode(e.target.value)} sx={{ textAlign: 'center', fontSize: '1.2em', letterSpacing: verificationCode.length === 0 ? '' : '0.4em', }} slotProps={{ input: { maxLength: 6, pattern: '[0-9]*', }, }} /> {error && {error}} {/* Backup Codes Modal */} setBackupCodesModalOpen(false)} > New Backup Codes Your previous backup codes are now invalid. Save these new codes in a safe place. Each code can only be used once. {backupCodes?.map((code, index) => ( {code} ))}
) } export default MFASettings