feat(security): add Cloudflare Turnstile CAPTCHA to registration form
- Add Turnstile widget rendering in RegisterPage on step 2 - Implement programmatic widget initialization with callbacks - Add token validation before form submission - Update AuthContext and API service to pass turnstileToken - Add backend verification via Cloudflare API in register controller - Include client IP in verification request - Add validation rule for turnstileToken - Reset widget on registration error
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { wsdcAPI } from '../services/api';
|
||||
@@ -32,6 +32,8 @@ const RegisterPage = () => {
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [turnstileToken, setTurnstileToken] = useState('');
|
||||
const turnstileRef = useRef(null);
|
||||
|
||||
const { register } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
@@ -78,6 +80,58 @@ const RegisterPage = () => {
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [wsdcId]);
|
||||
|
||||
// Setup Turnstile callbacks and render widget when on step 2
|
||||
useEffect(() => {
|
||||
if (step !== 2) return;
|
||||
|
||||
// Callback when Turnstile verification is successful
|
||||
window.onTurnstileSuccess = (token) => {
|
||||
setTurnstileToken(token);
|
||||
};
|
||||
|
||||
// Callback when Turnstile encounters an error
|
||||
window.onTurnstileError = () => {
|
||||
setError('CAPTCHA verification failed. Please try again.');
|
||||
setTurnstileToken('');
|
||||
};
|
||||
|
||||
// Wait for Turnstile script to load and render widget
|
||||
const renderTurnstile = () => {
|
||||
if (window.turnstile && turnstileRef.current && !turnstileRef.current.hasChildNodes()) {
|
||||
window.turnstile.render(turnstileRef.current, {
|
||||
sitekey: import.meta.env.VITE_TURNSTILE_SITE_KEY,
|
||||
callback: window.onTurnstileSuccess,
|
||||
'error-callback': window.onTurnstileError,
|
||||
theme: 'light',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Check if script is already loaded
|
||||
if (window.turnstile) {
|
||||
renderTurnstile();
|
||||
} else {
|
||||
// Wait for script to load
|
||||
const checkTurnstile = setInterval(() => {
|
||||
if (window.turnstile) {
|
||||
renderTurnstile();
|
||||
clearInterval(checkTurnstile);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearInterval(checkTurnstile);
|
||||
delete window.onTurnstileSuccess;
|
||||
delete window.onTurnstileError;
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
delete window.onTurnstileSuccess;
|
||||
delete window.onTurnstileError;
|
||||
};
|
||||
}, [step]);
|
||||
|
||||
// Handle WSDC ID confirmation and continue to registration
|
||||
const handleWsdcContinue = () => {
|
||||
if (!wsdcPreview) {
|
||||
@@ -130,6 +184,12 @@ const RegisterPage = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate Turnstile token
|
||||
if (!turnstileToken) {
|
||||
setError('Please complete the CAPTCHA verification');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
@@ -139,11 +199,17 @@ const RegisterPage = () => {
|
||||
formData.password,
|
||||
formData.firstName || null,
|
||||
formData.lastName || null,
|
||||
wsdcData?.wsdcId || null
|
||||
wsdcData?.wsdcId || null,
|
||||
turnstileToken
|
||||
);
|
||||
navigate('/events');
|
||||
} catch (err) {
|
||||
setError(err.message || 'Registration failed');
|
||||
// Reset Turnstile on error
|
||||
if (window.turnstile && turnstileRef.current) {
|
||||
window.turnstile.reset(turnstileRef.current);
|
||||
setTurnstileToken('');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -376,6 +442,11 @@ const RegisterPage = () => {
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Cloudflare Turnstile CAPTCHA */}
|
||||
<div>
|
||||
<div ref={turnstileRef}></div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 pt-2">
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
|
||||
Reference in New Issue
Block a user