Recuperación de cuenta en Next.js (3 flujos + reset), con Turnstile

- lib/recover.ts: requestRecovery(type,email) -> password (crea token en
  home_passwordreset + email), accountname (email con las cuentas), activation
  (reenvía enlace); respuesta genérica anti-enumeración. resetPassword(token,pass)
  re-deriva el verifier SRP6 v2 (bnetMakeRegistration) y actualiza battlenet_accounts,
  marca el token usado.
- Routes /api/auth/recover (Turnstile) y /api/auth/reset-password.
- Páginas app/[locale]/recover (RecoverForm: selector de tipo + email + Turnstile) y
  reset-password (ResetForm con token de la URL). Catálogos Recover, Reset.

Verificado: recover sin captcha -> captchaFailed; reset token inválido -> invalidLink;
páginas 200. Reutiliza home_passwordreset de Django. Crypto de reset ya validada.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 23:07:16 +00:00
parent aa61f79a55
commit 3573b7eb40
9 changed files with 391 additions and 0 deletions
+29
View File
@@ -81,5 +81,34 @@
"invalidLink": "The link is invalid or has already been used.",
"expiredLink": "The link has expired. Please request a new one.",
"goLogin": "Go to sign in"
},
"Recover": {
"title": "Recover account",
"type": "What do you want to recover?",
"typePassword": "Password",
"typeAccountName": "Account name",
"typeActivation": "Activation link",
"email": "Gmail email address",
"submit": "Request",
"sending": "Sending…",
"success": "If an account exists with that email, we have sent you the information.",
"invalidEmail": "Enter a valid Gmail address.",
"genericError": "Something went wrong. Please try again later.",
"captchaFailed": "Anti-bot verification failed. Please retry."
},
"Reset": {
"title": "Reset password",
"password": "New password",
"confPassword": "Confirm password",
"submit": "Reset",
"saving": "Saving…",
"success": "Password reset. You can now sign in.",
"goLogin": "Go to sign in",
"invalidLink": "The link is invalid or has already been used.",
"expiredLink": "The link has expired. Please request a new one.",
"passwordMismatch": "Passwords do not match.",
"passwordTooLong": "The password must not exceed 16 characters.",
"accountNotFound": "Account not found.",
"genericError": "Something went wrong. Please try again later."
}
}
+29
View File
@@ -81,5 +81,34 @@
"invalidLink": "El enlace no es válido o ya se ha usado.",
"expiredLink": "El enlace ha caducado. Solicita uno nuevo.",
"goLogin": "Ir a iniciar sesión"
},
"Recover": {
"title": "Recuperar cuenta",
"type": "¿Qué quieres recuperar?",
"typePassword": "Contraseña",
"typeAccountName": "Nombre de cuenta",
"typeActivation": "Enlace de activación",
"email": "Correo electrónico de Gmail",
"submit": "Solicitar",
"sending": "Enviando…",
"success": "Si existe una cuenta con ese correo, te hemos enviado la información.",
"invalidEmail": "Introduce un correo de Gmail válido.",
"genericError": "Algo ha salido mal. Inténtalo más tarde.",
"captchaFailed": "Verificación anti-bots fallida. Reintenta."
},
"Reset": {
"title": "Restablecer contraseña",
"password": "Nueva contraseña",
"confPassword": "Confirmar contraseña",
"submit": "Restablecer",
"saving": "Guardando…",
"success": "Contraseña restablecida. Ya puedes iniciar sesión.",
"goLogin": "Ir a iniciar sesión",
"invalidLink": "El enlace no es válido o ya se ha usado.",
"expiredLink": "El enlace ha caducado. Solicita uno nuevo.",
"passwordMismatch": "Las contraseñas no coinciden.",
"passwordTooLong": "La contraseña no debe exceder los 16 caracteres.",
"accountNotFound": "No se ha encontrado la cuenta.",
"genericError": "Algo ha salido mal. Inténtalo más tarde."
}
}