import type { Factor } from '@supabase/supabase-js'; import { IconShieldCheck as ShieldCheck, IconShieldOff as ShieldOff, IconDeviceMobile as Smartphone, } from '@tabler/icons-react'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router'; import { useAuth } from '@/components/auth/AuthProvider'; import { PageHeader } from '@/components/PageHeader'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'; import { Switch } from '@/components/ui/switch'; import { ContentPanel } from '../../components/ContentPanel'; export default function Security() { const { t } = useTranslation(); const { mfa, hasMfaEnabled, mfaRequired } = useAuth(); const [searchParams] = useSearchParams(); const [factors, setFactors] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); // Enrollment state const [enrolling, setEnrolling] = useState(false); const [enrollmentData, setEnrollmentData] = useState<{ id: string; qrCode: string; secret: string; } | null>(null); const [verificationCode, setVerificationCode] = useState(''); const [verifying, setVerifying] = useState(false); // Unenroll state const [unenrolling, setUnenrolling] = useState(false); const [factorToUnenroll, setFactorToUnenroll] = useState(null); // Show setup prompt if redirected from login with MFA required const setupRequired = searchParams.get('setup') === 'required'; const loadFactors = useCallback(async () => { try { const factorList = await mfa.listFactors(); setFactors(factorList.filter((f) => f.status === 'verified')); } catch { setError('Failed to load MFA factors'); } finally { setLoading(false); } }, [mfa]); // Load factors on mount useEffect(() => { loadFactors(); }, [loadFactors]); const handleStartEnrollment = async () => { setEnrolling(true); setError(''); try { const data = await mfa.enroll('Authenticator App'); if (data && data.type === 'totp' && 'totp' in data) { setEnrollmentData({ id: data.id, qrCode: data.totp.qr_code, secret: data.totp.secret, }); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to start enrollment'); setEnrolling(false); } }; const handleVerifyEnrollment = async () => { if (!enrollmentData || verificationCode.length !== 6) return; setVerifying(true); setError(''); try { // Create challenge and verify const challenge = await mfa.challenge(enrollmentData.id); if (challenge) { await mfa.verify(enrollmentData.id, challenge.id, verificationCode); } // Success - refresh factors and close dialog await loadFactors(); setEnrollmentData(null); setEnrolling(false); setVerificationCode(''); } catch (err) { setError(err instanceof Error ? err.message : 'Invalid verification code'); setVerificationCode(''); } finally { setVerifying(false); } }; const handleCancelEnrollment = async () => { // If we have an unverified factor, unenroll it if (enrollmentData) { try { await mfa.unenroll(enrollmentData.id); } catch { // Ignore error - factor may not exist } } setEnrollmentData(null); setEnrolling(false); setVerificationCode(''); setError(''); }; const handleUnenroll = async () => { if (!factorToUnenroll) return; setUnenrolling(true); setError(''); try { await mfa.unenroll(factorToUnenroll); await loadFactors(); setFactorToUnenroll(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to disable 2FA'); } finally { setUnenrolling(false); } }; return ( <> {setupRequired && !hasMfaEnabled && (

{t('security.mfaRequired')}

{t('security.mfaRequiredDescription')}

)} {error && (
{error}
)}
{t('security.twoFactor')} {t('security.twoFactorDescription')}
{!loading && ( { if (checked) { handleStartEnrollment(); } else if (factors.length > 0) { setFactorToUnenroll(factors[0].id); } }} /> )}
{!loading && (hasMfaEnabled || mfaRequired) && ( {hasMfaEnabled && ( <>

{t('security.mfaEnabledDescription')}

{factors.length > 0 && (

{t('security.enrolledAuthenticators')}

{factors.map((factor) => (
{factor.friendly_name || t('security.authenticatorApp')}
))}
)} )} {mfaRequired && (

{t('security.mfaRequiredByOrg')}

)}
)}
{/* Enrollment Dialog */} handleCancelEnrollment()}> {t('security.setupMfa')} {t('security.setupMfaDescription')}
{/* QR Code */}
{enrollmentData?.qrCode && (
)}
{/* Manual entry */}

{t('security.cantScan')}

{enrollmentData?.secret}
{/* Verification code input */}

{t('security.enterCode')}

{error && (
{error}
)}
{/* Unenroll Confirmation Dialog */} setFactorToUnenroll(null)}> {t('security.disableMfa')} {t('security.disableMfaDescription')} {mfaRequired && (
{t('security.disableMfaWarning')}
)}
); }