import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; 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 { useToast } from '@/hooks/use-toast'; import { useAuth } from '@/hooks/useAuth'; import { supabase } from '@/integrations/supabase/client'; import { Smartphone, Shield, Copy, Eye, EyeOff, Trash2 } from 'lucide-react'; import { MFARemovalDialog } from './MFARemovalDialog'; import { setStepUpRequired, getAuthMethod } from '@/lib/sessionFlags'; import { useNavigate } from 'react-router-dom'; import type { MFAFactor } from '@/types/auth'; export function TOTPSetup() { const { user } = useAuth(); const { toast } = useToast(); const navigate = useNavigate(); const [factors, setFactors] = useState([]); const [loading, setLoading] = useState(false); const [enrolling, setEnrolling] = useState(false); const [qrCode, setQrCode] = useState(''); const [secret, setSecret] = useState(''); const [factorId, setFactorId] = useState(''); const [verificationCode, setVerificationCode] = useState(''); const [showSecret, setShowSecret] = useState(false); const [showRemovalDialog, setShowRemovalDialog] = useState(false); useEffect(() => { fetchTOTPFactors(); }, [user]); const fetchTOTPFactors = async () => { if (!user) return; try { const { data, error } = await supabase.auth.mfa.listFactors(); if (error) throw error; const totpFactors = (data.totp || []).map(factor => ({ id: factor.id, friendly_name: factor.friendly_name || 'Authenticator App', factor_type: 'totp' as const, status: factor.status as 'verified' | 'unverified', created_at: factor.created_at, updated_at: factor.updated_at })); setFactors(totpFactors); } catch (error: any) { console.error('Error fetching TOTP factors:', error); } }; const startEnrollment = async () => { if (!user) return; setLoading(true); try { const { data, error } = await supabase.auth.mfa.enroll({ factorType: 'totp', friendlyName: 'Authenticator App' }); if (error) throw error; setQrCode(data.totp.qr_code); setSecret(data.totp.secret); setFactorId(data.id); setEnrolling(true); } catch (error: any) { toast({ title: 'Error', description: error.message || 'Failed to start TOTP enrollment', variant: 'destructive' }); } finally { setLoading(false); } }; const verifyAndEnable = async () => { if (!factorId || !verificationCode.trim()) { toast({ title: 'Error', description: 'Please enter the verification code', variant: 'destructive' }); return; } setLoading(true); try { // Step 1: Create a challenge first const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({ factorId }); if (challengeError) throw challengeError; // Step 2: Verify using the challengeId from the challenge response const { error: verifyError } = await supabase.auth.mfa.verify({ factorId, challengeId: challengeData.id, code: verificationCode.trim() }); if (verifyError) throw verifyError; // Check if user signed in via OAuth and trigger step-up flow const authMethod = getAuthMethod(); const isOAuthUser = authMethod === 'oauth'; if (isOAuthUser) { console.log('[TOTPSetup] OAuth user enrolled MFA, triggering step-up...'); setStepUpRequired(true, window.location.pathname); navigate('/auth/mfa-step-up'); return; } toast({ title: 'TOTP Enabled', description: isOAuthUser ? 'Please verify with your authenticator code to continue.' : 'Please sign in again to activate MFA protection.' }); if (isOAuthUser) { // Already handled above with navigate return; } else { // For email/password users, force sign out to require MFA on next login setTimeout(async () => { await supabase.auth.signOut(); window.location.href = '/auth'; }, 2000); } } catch (error: any) { toast({ title: 'Error', description: error.message || 'Invalid verification code. Please try again.', variant: 'destructive' }); } finally { setLoading(false); } }; const handleRemovalSuccess = async () => { await fetchTOTPFactors(); }; const copySecret = () => { navigator.clipboard.writeText(secret); toast({ title: 'Copied', description: 'Secret key copied to clipboard' }); }; const cancelEnrollment = () => { setEnrolling(false); setQrCode(''); setSecret(''); setFactorId(''); setVerificationCode(''); }; const activeFactor = factors.find(f => f.status === 'verified'); if (enrolling) { return ( Set Up Authenticator App Scan the QR code with your authenticator app, then enter the verification code below. {/* QR Code */}
TOTP QR Code
{/* Manual Entry */}
{/* Verification */}
setVerificationCode(e.target.value)} placeholder="000000" maxLength={6} className="text-center text-lg tracking-widest font-mono" />
); } return ( Add an extra layer of security to your account with two-factor authentication. {activeFactor ? (
Two-factor authentication is enabled for your account. You'll be prompted for a verification code when signing in.

{activeFactor.friendly_name || 'Authenticator App'}

Enabled {new Date(activeFactor.created_at).toLocaleDateString()}

Active
) : (

Authenticator App

Use an authenticator app to generate verification codes

)}
); }