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
+22
View File
@@ -0,0 +1,22 @@
import { requestRecovery } from '@/lib/recover'
import { verifyTurnstile } from '@/lib/turnstile'
export async function POST(request: Request) {
let type = ''
let email = ''
let turnstileToken = ''
try {
const body = await request.json()
type = String(body.type ?? '')
email = String(body.email ?? '')
turnstileToken = String(body.turnstileToken ?? '')
} catch {
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
}
const ip = (request.headers.get('x-forwarded-for') || '').split(',')[0].trim()
if (!(await verifyTurnstile(turnstileToken, ip))) {
return Response.json({ success: false, error: 'captchaFailed' })
}
const result = await requestRecovery(type, email)
return Response.json(result)
}
@@ -0,0 +1,18 @@
import { resetPassword } from '@/lib/recover'
export async function POST(request: Request) {
let token = ''
let password = ''
let confPassword = ''
try {
const body = await request.json()
token = String(body.token ?? '')
password = String(body.password ?? '').trim()
confPassword = String(body.confPassword ?? '').trim()
} catch {
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
}
if (!token) return Response.json({ success: false, error: 'invalidLink' })
const result = await resetPassword(token, password, confPassword)
return Response.json(result)
}