aa61f79a55
- 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>
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import { authenticate, getGameAccounts } from '@/lib/auth'
|
|
import { getSession } from '@/lib/session'
|
|
import { setGameAccountSession } from '@/lib/account-session'
|
|
import { verifyTurnstile } from '@/lib/turnstile'
|
|
|
|
export async function POST(request: Request) {
|
|
let email = ''
|
|
let password = ''
|
|
let turnstileToken = ''
|
|
try {
|
|
const body = await request.json()
|
|
email = String(body.email ?? '').trim()
|
|
password = String(body.password ?? '')
|
|
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' })
|
|
}
|
|
|
|
if (!email || !password) {
|
|
return Response.json({ success: false, error: 'missingFields' })
|
|
}
|
|
|
|
const account = await authenticate(email, password)
|
|
if (!account) {
|
|
return Response.json({ success: false, error: 'invalidCredentials' })
|
|
}
|
|
|
|
const session = await getSession()
|
|
session.bnetId = account.id
|
|
session.bnetEmail = account.email
|
|
|
|
const games = await getGameAccounts(account.id)
|
|
let needsSelection = true
|
|
if (games.length === 1) {
|
|
setGameAccountSession(session, games[0])
|
|
needsSelection = false
|
|
} else {
|
|
// 0 o >1 cuentas de juego: dejar la selección pendiente
|
|
delete session.username
|
|
delete session.accountId
|
|
}
|
|
await session.save()
|
|
|
|
return Response.json({ success: true, needsSelection })
|
|
}
|