Cloudflare Turnstile en login y registro (Next.js)
- lib/turnstile.ts: verifyTurnstile(token, ip) contra siteverify (fail-closed). - components/Turnstile.tsx: widget cliente (carga el script de Cloudflare, callback con el token). Site key por NEXT_PUBLIC_TURNSTILE_SITE_KEY; secreto server-only. - LoginForm/RegisterForm: widget + token en el POST; submit deshabilitado hasta resolver el captcha (si hay site key). - Routes login/register: verifican el token antes de continuar (error captchaFailed). - Catálogos: captchaFailed. Verificado: POST sin token -> captchaFailed en login y registro (enforcement server). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Verificación server-side del token de Cloudflare Turnstile.
|
||||
export async function verifyTurnstile(token: string, ip?: string): Promise<boolean> {
|
||||
const secret = process.env.TURNSTILE_SECRET_KEY
|
||||
if (!secret) return true // captcha desactivado si no hay secreto
|
||||
if (!token) return false
|
||||
try {
|
||||
const body = new URLSearchParams({ secret, response: token })
|
||||
if (ip) body.set('remoteip', ip)
|
||||
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString(),
|
||||
})
|
||||
const data: { success?: boolean } = await res.json()
|
||||
return Boolean(data.success)
|
||||
} catch {
|
||||
return false // fail-closed
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user