diff --git a/src/components/settings/PasswordUpdateDialog.tsx b/src/components/settings/PasswordUpdateDialog.tsx index 59799c6f..328c8ff0 100644 --- a/src/components/settings/PasswordUpdateDialog.tsx +++ b/src/components/settings/PasswordUpdateDialog.tsx @@ -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('password'); const [loading, setLoading] = useState(false); const [nonce, setNonce] = useState(''); const [newPassword, setNewPassword] = useState(''); const [hasMFA, setHasMFA] = useState(false); const [totpCode, setTotpCode] = useState(''); + const [captchaToken, setCaptchaToken] = useState(''); + const [captchaKey, setCaptchaKey] = useState(0); const form = useForm({ 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 )} +
+ setCaptchaToken('')} + onExpire={() => setCaptchaToken('')} + siteKey={import.meta.env.VITE_TURNSTILE_SITE_KEY} + theme={theme === 'dark' ? 'dark' : 'light'} + size="normal" + /> +
+ -