mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 12:31:12 -05:00
98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
import { useState } from "react";
|
|
import { supabase } from "@/lib/supabaseClient";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { handleError } from "@/lib/errorHandler";
|
|
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 handleVerify = async () => {
|
|
if (code.length !== 6) return;
|
|
|
|
setLoading(true);
|
|
try {
|
|
// Create fresh challenge for each verification attempt
|
|
const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({ factorId });
|
|
|
|
if (challengeError) throw challengeError;
|
|
|
|
// Immediately verify with fresh challenge
|
|
const { data, error } = await supabase.auth.mfa.verify({
|
|
factorId,
|
|
challengeId: challengeData.id,
|
|
code: code.trim(),
|
|
});
|
|
|
|
if (error) throw error;
|
|
|
|
if (data) {
|
|
toast({
|
|
title: "Welcome back!",
|
|
description: "Multi-Factor verification successful.",
|
|
});
|
|
onSuccess();
|
|
}
|
|
} catch (error: unknown) {
|
|
handleError(error, {
|
|
action: 'MFA Verification',
|
|
metadata: {
|
|
factorId,
|
|
codeLength: code.length,
|
|
context: 'MFAChallenge'
|
|
}
|
|
});
|
|
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">Multi-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>
|
|
);
|
|
}
|