import { useState, useEffect } from 'react'; import { supabase } from '@/lib/supabaseClient'; import { invokeWithTracking } from '@/lib/edgeFunctionTracking'; import { useToast } from '@/hooks/use-toast'; import { getErrorMessage } from '@/lib/errorHandler'; import { useRequireMFA } from '@/hooks/useRequireMFA'; import { getSessionAAL } from '@/types/supabase-session'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Shield, AlertTriangle } from 'lucide-react'; interface MFARemovalDialogProps { open: boolean; onOpenChange: (open: boolean) => void; factorId: string; onSuccess: () => void; } export function MFARemovalDialog({ open, onOpenChange, factorId, onSuccess }: MFARemovalDialogProps) { const { requiresMFA } = useRequireMFA(); const { toast } = useToast(); const [step, setStep] = useState<'password' | 'totp' | 'confirm'>('password'); const [password, setPassword] = useState(''); const [totpCode, setTotpCode] = useState(''); const [loading, setLoading] = useState(false); // Phase 1: Check AAL2 requirement on dialog open useEffect(() => { if (open) { const checkAalLevel = async (): Promise => { const { data: { session } } = await supabase.auth.getSession(); const currentAal = getSessionAAL(session); if (currentAal !== 'aal2') { toast({ title: 'Multi-Factor Authentication Required', description: 'Please verify your identity with Multi-Factor Authentication before making security changes', variant: 'destructive' }); onOpenChange(false); } }; checkAalLevel(); } }, [open, onOpenChange]); const handleClose = () => { setStep('password'); setPassword(''); setTotpCode(''); setLoading(false); onOpenChange(false); }; const handlePasswordVerification = async () => { if (!password.trim()) { toast({ title: 'Password Required', description: 'Please enter your password', variant: 'destructive' }); return; } setLoading(true); try { // Get current user email const { data: { user } } = await supabase.auth.getUser(); if (!user?.email) throw new Error('User email not found'); // Re-authenticate with password const { error } = await supabase.auth.signInWithPassword({ email: user.email, password: password }); if (error) throw error; toast({ title: 'Password Verified', description: 'Password verified successfully' }); setStep('totp'); } catch (error: unknown) { toast({ title: 'Error', description: getErrorMessage(error), variant: 'destructive' }); } finally { setLoading(false); } }; const handleTOTPVerification = async () => { if (!totpCode.trim() || totpCode.length !== 6) { toast({ title: 'Invalid Code', description: 'Please enter a valid 6-digit code', variant: 'destructive' }); return; } setLoading(true); try { // Create challenge const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({ factorId }); if (challengeError) throw challengeError; // Verify TOTP code const { error: verifyError } = await supabase.auth.mfa.verify({ factorId, challengeId: challengeData.id, code: totpCode.trim() }); if (verifyError) throw verifyError; // Phase 1: Verify session is at AAL2 after TOTP verification const { data: { session } } = await supabase.auth.getSession(); const currentAal = getSessionAAL(session); if (currentAal !== 'aal2') { throw new Error('Session must be at AAL2 to remove MFA'); } toast({ title: 'Code Verified', description: 'TOTP code verified successfully' }); setStep('confirm'); } catch (error: unknown) { toast({ title: 'Error', description: getErrorMessage(error), variant: 'destructive' }); } finally { setLoading(false); } }; const handleMFARemoval = async () => { // Phase 2: Check if user's role requires MFA if (requiresMFA) { toast({ title: 'Multi-Factor Authentication Required', description: 'Your role requires Multi-Factor Authentication and it cannot be disabled', variant: 'destructive' }); handleClose(); return; } setLoading(true); try { // Phase 3: Call edge function instead of direct unenroll const { data, error, requestId } = await invokeWithTracking( 'mfa-unenroll', { factorId }, (await supabase.auth.getUser()).data.user?.id ); if (error) throw error; if (data?.error) throw new Error(data.error); toast({ title: 'Multi-Factor Authentication Disabled', description: 'Multi-Factor Authentication has been disabled' }); handleClose(); onSuccess(); } catch (error: unknown) { toast({ title: 'Error', description: getErrorMessage(error), variant: 'destructive' }); } finally { setLoading(false); } }; return ( Disable Multi-Factor Authentication
Disabling Multi-Factor Authentication will make your account less secure. You'll need to verify your identity first. {step === 'password' && (

Step 1 of 3: Enter your password to continue

setPassword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handlePasswordVerification()} placeholder="Enter your password" disabled={loading} />
)} {step === 'totp' && (

Step 2 of 3: Enter your current code

setTotpCode(e.target.value.replace(/\D/g, '').slice(0, 6))} onKeyDown={(e) => e.key === 'Enter' && handleTOTPVerification()} placeholder="000000" maxLength={6} disabled={loading} className="text-center text-2xl tracking-widest" />
)} {step === 'confirm' && (

Step 3 of 3: Final confirmation

Are you sure you want to disable Multi-Factor Authentication? This will:

  • Remove Multi-Factor Authentication protection from your account
  • Send a security notification email
  • Log this action in your security history
)}
Cancel {step === 'password' && ( )} {step === 'totp' && ( )} {step === 'confirm' && ( {loading ? 'Disabling...' : 'Disable Multi-Factor Authentication'} )}
); }