diff --git a/src/components/auth/MFARemovalDialog.tsx b/src/components/auth/MFARemovalDialog.tsx new file mode 100644 index 00000000..f0e86d34 --- /dev/null +++ b/src/components/auth/MFARemovalDialog.tsx @@ -0,0 +1,245 @@ +import { useState } from 'react'; +import { supabase } from '@/integrations/supabase/client'; +import { toast } from 'sonner'; +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 [step, setStep] = useState<'password' | 'totp' | 'confirm'>('password'); + const [password, setPassword] = useState(''); + const [totpCode, setTotpCode] = useState(''); + const [loading, setLoading] = useState(false); + + const handleClose = () => { + setStep('password'); + setPassword(''); + setTotpCode(''); + setLoading(false); + onOpenChange(false); + }; + + const handlePasswordVerification = async () => { + if (!password.trim()) { + toast.error('Please enter your password'); + 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.success('Password verified'); + setStep('totp'); + } catch (error: any) { + console.error('Password verification failed:', error); + toast.error('Invalid password. Please try again.'); + } finally { + setLoading(false); + } + }; + + const handleTOTPVerification = async () => { + if (!totpCode.trim() || totpCode.length !== 6) { + toast.error('Please enter a valid 6-digit code'); + 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; + + toast.success('TOTP code verified'); + setStep('confirm'); + } catch (error: any) { + console.error('TOTP verification failed:', error); + toast.error('Invalid code. Please try again.'); + } finally { + setLoading(false); + } + }; + + const handleMFARemoval = async () => { + setLoading(true); + try { + // Unenroll the factor + const { error } = await supabase.auth.mfa.unenroll({ factorId }); + if (error) throw error; + + // Log the action + const { data: { user } } = await supabase.auth.getUser(); + if (user) { + await supabase.from('admin_audit_log').insert({ + admin_user_id: user.id, + target_user_id: user.id, + action: 'mfa_disabled', + details: { + factor_id: factorId, + timestamp: new Date().toISOString(), + user_agent: navigator.userAgent + } + }); + + // Trigger email notification + await supabase.functions.invoke('trigger-notification', { + body: { + userId: user.id, + workflowId: 'security-alert', + payload: { + action: 'MFA Disabled', + message: 'Two-factor authentication has been disabled on your account.', + timestamp: new Date().toISOString() + } + } + }).catch(err => console.error('Notification error:', err)); + } + + toast.success('Two-factor authentication has been disabled'); + handleClose(); + onSuccess(); + } catch (error: any) { + console.error('MFA removal failed:', error); + toast.error('Failed to disable MFA. Please try again.'); + } finally { + setLoading(false); + } + }; + + return ( + + + + + + Disable Two-Factor Authentication + + +
+ + + + Disabling MFA 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 TOTP 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 two-factor authentication? This will: +

+
    +
  • Remove TOTP 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 MFA'} + + )} + +
+
+ ); +} diff --git a/src/components/auth/TOTPSetup.tsx b/src/components/auth/TOTPSetup.tsx index 1ceb16c2..619292ed 100644 --- a/src/components/auth/TOTPSetup.tsx +++ b/src/components/auth/TOTPSetup.tsx @@ -5,11 +5,11 @@ import { Label } from '@/components/ui/label'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { useToast } from '@/hooks/use-toast'; import { useAuth } from '@/hooks/useAuth'; import { supabase } from '@/integrations/supabase/client'; -import { Smartphone, Shield, Copy, Eye, EyeOff, Trash2, AlertCircle } from 'lucide-react'; +import { Smartphone, Shield, Copy, Eye, EyeOff, Trash2 } from 'lucide-react'; +import { MFARemovalDialog } from './MFARemovalDialog'; interface TOTPFactor { id: string; @@ -30,6 +30,7 @@ export function TOTPSetup() { const [factorId, setFactorId] = useState(''); const [verificationCode, setVerificationCode] = useState(''); const [showSecret, setShowSecret] = useState(false); + const [showRemovalDialog, setShowRemovalDialog] = useState(false); useEffect(() => { fetchTOTPFactors(); @@ -133,30 +134,8 @@ export function TOTPSetup() { } }; - const unenrollFactor = async (factorId: string) => { - setLoading(true); - try { - const { error } = await supabase.auth.mfa.unenroll({ - factorId - }); - - if (error) throw error; - - toast({ - title: 'TOTP Disabled', - description: 'Two-factor authentication has been disabled for your account.' - }); - - fetchTOTPFactors(); - } catch (error: any) { - toast({ - title: 'Error', - description: error.message || 'Failed to disable TOTP', - variant: 'destructive' - }); - } finally { - setLoading(false); - } + const handleRemovalSuccess = async () => { + await fetchTOTPFactors(); }; const copySecret = () => { @@ -281,36 +260,23 @@ export function TOTPSetup() { Active - - - - - - - - - Disable Two-Factor Authentication - - - Are you sure you want to disable two-factor authentication? This will make your account less secure. - - - - Cancel - unenrollFactor(activeFactor.id)} - className="bg-destructive hover:bg-destructive/90" - > - Disable TOTP - - - - + + + ) : (