4f8f101ba8
`registerAccount` ya rechazaba un correo con cuenta bnet, pero `activateAccount` insertaba SIN volver a comprobarlo, y entre registrarse y activar el correo puede dejar de estar libre. Como `battlenet_accounts.email` no tiene índice único, la BD tampoco lo frenaba: hay 3 filas TEST@TEST.COM de 2024 que lo demuestran. Ahora se recomprueba antes del INSERT y se borra la activación, que ya no sirve. Nuevo error `emailExists` (ES/EN) para no soltar un «enlace inválido» que despista. MAX_GAME_ACCOUNTS sube a lib/bnet.ts y lo usa el correo de activación: el texto prometía 10 cuentas mientras el código cortaba en 8, justo por estar el número escrito a mano en los dos sitios. El límite del panel ya funcionaba (API + aviso «Has alcanzado el máximo de cuentas»); solo se centraliza la constante. Verificado en producción con una activación pendiente de un correo que ya tenía bnet: devuelve emailExists, no crea la cuenta y el contador no sube. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
import type { ResultSetHeader, RowDataPacket } from 'mysql2'
|
|
import { db, DB } from '@/lib/db'
|
|
import { getSession } from '@/lib/session'
|
|
import { authenticate } from '@/lib/auth'
|
|
import { gameMakeRegistration, makeGameAccountUsername, normalizeEmail, MAX_GAME_ACCOUNTS } from '@/lib/bnet'
|
|
|
|
|
|
/**
|
|
* Crea una nueva cuenta de juego (WoW2, WoW3…) bajo la Battle.net de la sesión.
|
|
* Requiere la contraseña (para derivar el SRP6 de la cuenta y que sirva in-game,
|
|
* ya que todas las cuentas de la bnet usan la misma contraseña).
|
|
*/
|
|
export async function POST(request: Request) {
|
|
const session = await getSession()
|
|
if (!session.bnetId || !session.bnetEmail) {
|
|
return Response.json({ success: false, error: 'notAuthenticated' }, { status: 401 })
|
|
}
|
|
|
|
let password = ''
|
|
try {
|
|
const body = await request.json()
|
|
password = String(body.password ?? '')
|
|
} catch {
|
|
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
|
|
}
|
|
if (!password) return Response.json({ success: false, error: 'invalidPassword' })
|
|
|
|
// Verifica la contraseña contra la Battle.net.
|
|
const bnet = await authenticate(session.bnetEmail, password)
|
|
if (!bnet) return Response.json({ success: false, error: 'invalidPassword' })
|
|
|
|
const emailNorm = normalizeEmail(session.bnetEmail)
|
|
|
|
// Siguiente índice de cuenta de juego.
|
|
const [rows] = await db(DB.auth).query<RowDataPacket[]>(
|
|
'SELECT COALESCE(MAX(battlenet_index), 0) AS maxIdx, COUNT(*) AS n FROM account WHERE battlenet_account = ?',
|
|
[session.bnetId],
|
|
)
|
|
const count = Number(rows[0]?.n ?? 0)
|
|
if (count >= MAX_GAME_ACCOUNTS) {
|
|
return Response.json({ success: false, error: 'maxAccounts' })
|
|
}
|
|
const nextIndex = Number(rows[0]?.maxIdx ?? 0) + 1
|
|
|
|
// Crea la cuenta de juego (SRP6 Grunt con la contraseña de la bnet).
|
|
const gameUsername = makeGameAccountUsername(session.bnetId, nextIndex)
|
|
const game = gameMakeRegistration(gameUsername, password)
|
|
try {
|
|
await db(DB.auth).query<ResultSetHeader>(
|
|
'INSERT INTO account (username, salt, verifier, email, reg_mail, joindate, last_ip, expansion, battlenet_account, battlenet_index) ' +
|
|
'VALUES (?, ?, ?, ?, ?, NOW(), ?, ?, ?, ?)',
|
|
[gameUsername, game.salt, game.verifier, emailNorm, emailNorm, '0.0.0.0', 2, session.bnetId, nextIndex],
|
|
)
|
|
} catch {
|
|
return Response.json({ success: false, error: 'createFailed' })
|
|
}
|
|
|
|
return Response.json({ success: true, index: nextIndex })
|
|
}
|