import QRCode from 'qrcode' import { getSession } from '@/lib/session' import { authenticate } from '@/lib/auth' import { checkSecurityToken } from '@/lib/security-token' import { getRealmName } from '@/lib/realm' import { base32Encode, otpauthUrl, generateSecret, validateToken, is2faEnabled, setAccountTotpSecret, clearAccountTotpSecret, getAccountTotpSecret, } from '@/lib/two-factor' // "15#1" -> "WOW1" (índice tras '#'); si no hay índice, usa el username tal cual. function wowLabel(username: string): string { const idx = username.split('#')[1] return idx ? `WOW${idx}` : username } export async function POST(request: Request) { const session = await getSession() if (!session.bnetId || !session.accountId || !session.bnetEmail || !session.username) { return Response.json({ success: false, message: 'Tu sesión ha expirado. Inicia sesión de nuevo.' }, { status: 401 }) } const accountId = session.accountId let body: Record = {} try { body = await request.json() } catch { return Response.json({ success: false, message: 'Solicitud no válida.' }, { status: 400 }) } const action = String(body.action ?? '') /* ------------------------------- Activar 2FA ------------------------------ */ if (action === 'activate') { if (await is2faEnabled(accountId)) { return Response.json({ success: false, message: 'La verificación en 2 pasos ya está activada en esta cuenta.' }) } const password = String(body.password ?? '') const securityToken = String(body.securityToken ?? '') if (!password || !securityToken) { return Response.json({ success: false, message: 'Introduce tu contraseña y tu token de seguridad.' }) } const bnet = await authenticate(session.bnetEmail, password) if (!bnet) return Response.json({ success: false, message: 'Contraseña incorrecta.' }) if (!(await checkSecurityToken(accountId, securityToken))) { return Response.json({ success: false, message: 'Token de seguridad incorrecto. Puedes solicitar uno nuevo.' }) } const secret = generateSecret() const secretBase32 = base32Encode(secret) const realm = await getRealmName() const url = otpauthUrl(realm, wowLabel(session.username), secretBase32) const qrCodeUrl = await QRCode.toDataURL(url, { margin: 1, width: 220 }) // Guardamos el secreto como PENDIENTE (no se escribe en la cuenta hasta que // el usuario confirme un código válido; así se evita cualquier bloqueo). session.pending2fa = secret.toString('hex') session.pending2faFor = accountId await session.save() return Response.json({ success: true, message: 'Escanea el código QR con tu aplicación de autenticación y confirma un código para activar 2FA.', qrCodeUrl, secretKey: secretBase32, }) } /* ------------------------------- Verificar -------------------------------- */ if (action === 'verify') { if (!session.pending2fa || session.pending2faFor !== accountId) { return Response.json({ success: false, message: 'No hay una activación pendiente. Vuelve a empezar.' }) } const code = String(body.code ?? '').replace(/\D/g, '') if (code.length !== 6) return Response.json({ success: false, message: 'Introduce el código de 6 dígitos.' }) const secret = Buffer.from(session.pending2fa, 'hex') if (!validateToken(secret, Number(code))) { return Response.json({ success: false, message: 'Código incorrecto. Comprueba la hora de tu dispositivo e inténtalo de nuevo.' }) } await setAccountTotpSecret(accountId, secret) session.pending2fa = undefined session.pending2faFor = undefined await session.save() return Response.json({ success: true, message: '¡Verificación en 2 pasos activada! A partir de ahora se pedirá el código al iniciar sesión en el juego.' }) } /* ------------------------------- Desactivar ------------------------------- */ if (action === 'disable') { const secret = await getAccountTotpSecret(accountId) if (!secret) return Response.json({ success: false, message: 'La verificación en 2 pasos no está activada.' }) const code = String(body.code ?? '').replace(/\D/g, '') if (code.length !== 6) return Response.json({ success: false, message: 'Introduce el código de 6 dígitos de tu aplicación.' }) if (!validateToken(secret, Number(code))) { return Response.json({ success: false, message: 'Código incorrecto. No se ha desactivado 2FA.' }) } await clearAccountTotpSecret(accountId) return Response.json({ success: true, message: 'Verificación en 2 pasos desactivada.' }) } return Response.json({ success: false, message: 'Acción no válida.' }, { status: 400 }) }