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:
2026-07-12 23:04:22 +00:00
parent 4a783d165f
commit aa61f79a55
8 changed files with 134 additions and 9 deletions
+4 -2
View File
@@ -37,7 +37,8 @@
"genericError": "Something went wrong. Please try again later.",
"forgot": "I forgot my password/username",
"newHere": "New to the community? You can create an account",
"createAccount": "here"
"createAccount": "here",
"captchaFailed": "Anti-bot verification failed. Please retry."
},
"Account": {
"title": "My account",
@@ -71,7 +72,8 @@
"invalidEmail": "The email address is not valid.",
"emailExists": "An account with that email already exists.",
"recruiterNotFound": "The recruiter entered does not exist.",
"genericError": "Something went wrong. Please try again later."
"genericError": "Something went wrong. Please try again later.",
"captchaFailed": "Anti-bot verification failed. Please retry."
},
"Activate": {
"activating": "Activating your account…",
+4 -2
View File
@@ -37,7 +37,8 @@
"genericError": "Algo ha salido mal. Inténtalo más tarde.",
"forgot": "He olvidado mi contraseña/usuario",
"newHere": "¿Nuevo en la comunidad? Puedes crear una cuenta",
"createAccount": "aquí"
"createAccount": "aquí",
"captchaFailed": "Verificación anti-bots fallida. Reintenta."
},
"Account": {
"title": "Mi cuenta",
@@ -71,7 +72,8 @@
"invalidEmail": "El correo electrónico no es válido.",
"emailExists": "Ya existe una cuenta con ese correo electrónico.",
"recruiterNotFound": "El reclutador ingresado no existe.",
"genericError": "Algo ha salido mal. Inténtalo más tarde."
"genericError": "Algo ha salido mal. Inténtalo más tarde.",
"captchaFailed": "Verificación anti-bots fallida. Reintenta."
},
"Activate": {
"activating": "Activando tu cuenta…",