mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 19:11:12 -05:00
feat: Integrate CAPTCHA into password updates
This commit is contained in:
@@ -17,6 +17,8 @@ import { useToast } from '@/hooks/use-toast';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { Loader2, Shield, CheckCircle2 } from 'lucide-react';
|
||||
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
|
||||
import { TurnstileCaptcha } from '@/components/auth/TurnstileCaptcha';
|
||||
import { useTheme } from '@/components/theme/ThemeProvider';
|
||||
|
||||
const passwordSchema = z.object({
|
||||
currentPassword: z.string().min(1, 'Current password is required'),
|
||||
@@ -39,12 +41,15 @@ type Step = 'password' | 'mfa' | 'success';
|
||||
|
||||
export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: PasswordUpdateDialogProps) {
|
||||
const { toast } = useToast();
|
||||
const { theme } = useTheme();
|
||||
const [step, setStep] = useState<Step>('password');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [nonce, setNonce] = useState<string>('');
|
||||
const [newPassword, setNewPassword] = useState<string>('');
|
||||
const [hasMFA, setHasMFA] = useState(false);
|
||||
const [totpCode, setTotpCode] = useState('');
|
||||
const [captchaToken, setCaptchaToken] = useState<string>('');
|
||||
const [captchaKey, setCaptchaKey] = useState(0);
|
||||
|
||||
const form = useForm<PasswordFormData>({
|
||||
resolver: zodResolver(passwordSchema),
|
||||
@@ -75,15 +80,32 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
};
|
||||
|
||||
const onSubmit = async (data: PasswordFormData) => {
|
||||
if (!captchaToken) {
|
||||
toast({
|
||||
title: 'CAPTCHA Required',
|
||||
description: 'Please complete the CAPTCHA verification.',
|
||||
variant: 'destructive'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Step 1: Reauthenticate with current password to get a nonce
|
||||
const { error: signInError } = await supabase.auth.signInWithPassword({
|
||||
email: (await supabase.auth.getUser()).data.user?.email || '',
|
||||
password: data.currentPassword
|
||||
password: data.currentPassword,
|
||||
options: {
|
||||
captchaToken
|
||||
}
|
||||
});
|
||||
|
||||
if (signInError) throw signInError;
|
||||
if (signInError) {
|
||||
// Reset CAPTCHA on authentication failure
|
||||
setCaptchaToken('');
|
||||
setCaptchaKey(prev => prev + 1);
|
||||
throw signInError;
|
||||
}
|
||||
|
||||
// Step 2: Generate nonce for secure password update
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
@@ -215,6 +237,8 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
setStep('password');
|
||||
form.reset();
|
||||
setTotpCode('');
|
||||
setCaptchaToken('');
|
||||
setCaptchaKey(prev => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -279,11 +303,23 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<TurnstileCaptcha
|
||||
key={captchaKey}
|
||||
onSuccess={setCaptchaToken}
|
||||
onError={() => setCaptchaToken('')}
|
||||
onExpire={() => setCaptchaToken('')}
|
||||
siteKey={import.meta.env.VITE_TURNSTILE_SITE_KEY}
|
||||
theme={theme === 'dark' ? 'dark' : 'light'}
|
||||
size="normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={handleClose} disabled={loading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
<Button type="submit" disabled={loading || !captchaToken}>
|
||||
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{hasMFA ? 'Continue' : 'Update Password'}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user