feat: Implement MFA Challenge Support

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 13:39:05 +00:00
parent 3e520e1520
commit 121f7c533a
3 changed files with 198 additions and 3 deletions

View File

@@ -0,0 +1,124 @@
import { useState, useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
import { Shield } from 'lucide-react';
interface MFAChallengeProps {
factorId: string;
onSuccess: () => void;
onCancel: () => void;
}
export function MFAChallenge({ factorId, onSuccess, onCancel }: MFAChallengeProps) {
const { toast } = useToast();
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const [challengeId, setChallengeId] = useState<string | null>(null);
// Create MFA challenge on mount
useEffect(() => {
const createChallenge = async () => {
try {
const { data, error } = await supabase.auth.mfa.challenge({ factorId });
if (error) throw error;
setChallengeId(data.id);
} catch (error: any) {
toast({
variant: "destructive",
title: "MFA Challenge Failed",
description: error.message
});
onCancel();
}
};
createChallenge();
}, [factorId, onCancel, toast]);
const handleVerify = async () => {
if (code.length !== 6 || !challengeId) return;
setLoading(true);
try {
const { data, error } = await supabase.auth.mfa.verify({
factorId,
challengeId,
code
});
if (error) throw error;
if (data) {
toast({
title: "Welcome back!",
description: "MFA verification successful."
});
onSuccess();
}
} catch (error: any) {
toast({
variant: "destructive",
title: "Verification Failed",
description: error.message || "Invalid code. Please try again."
});
setCode('');
} finally {
setLoading(false);
}
};
return (
<div className="space-y-4">
<div className="flex items-center gap-2 text-primary">
<Shield className="w-5 h-5" />
<h3 className="font-semibold">Two-Factor Authentication</h3>
</div>
<p className="text-sm text-muted-foreground">
Enter the 6-digit code from your authenticator app
</p>
<div className="space-y-2">
<Label htmlFor="mfa-code">Authentication Code</Label>
<div className="flex justify-center">
<InputOTP
maxLength={6}
value={code}
onChange={setCode}
onComplete={handleVerify}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</div>
</div>
<div className="flex gap-2">
<Button
variant="outline"
onClick={onCancel}
className="flex-1"
disabled={loading}
>
Cancel
</Button>
<Button
onClick={handleVerify}
className="flex-1"
disabled={code.length !== 6 || loading}
>
{loading ? "Verifying..." : "Verify"}
</Button>
</div>
</div>
);
}