Impedir cuentas Battle.net duplicadas al activar; citar el límite real de 8
`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>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
const ERROR_KEYS = ['invalidLink', 'expiredLink'] as const
|
||||
const ERROR_KEYS = ['invalidLink', 'expiredLink', 'emailExists'] as const
|
||||
|
||||
export function ActivateClient({ hash, realm }: { hash: string; realm: string }) {
|
||||
const t = useTranslations('Activate')
|
||||
|
||||
@@ -2,9 +2,8 @@ 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 } from '@/lib/bnet'
|
||||
import { gameMakeRegistration, makeGameAccountUsername, normalizeEmail, MAX_GAME_ACCOUNTS } from '@/lib/bnet'
|
||||
|
||||
const MAX_GAME_ACCOUNTS = 8 // Battle.net: WoW1…WoW8
|
||||
|
||||
/**
|
||||
* Crea una nueva cuenta de juego (WoW2, WoW3…) bajo la Battle.net de la sesión.
|
||||
|
||||
@@ -147,6 +147,13 @@ export function makeGameAccountUsername(bnetId: number, index = 1): string {
|
||||
return `${bnetId}#${index}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuentas de juego por cuenta Battle.net (WoW1…WoW8). Vive aquí, y no en la ruta
|
||||
* que la aplica, para que el correo de activación cite el límite REAL: el texto
|
||||
* anterior prometía 10 mientras el código ya cortaba en 8.
|
||||
*/
|
||||
export const MAX_GAME_ACCOUNTS = 8
|
||||
|
||||
/**
|
||||
* Nombre visible de una cuenta de juego: «15#1» → «WOW1» (el nº tras # es la
|
||||
* cuenta de juego bajo la Battle.net; con varias serían WOW1, WOW2, WOW3…).
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
* entra con el correo, no con un usuario).
|
||||
*/
|
||||
|
||||
import { MAX_GAME_ACCOUNTS } from './bnet'
|
||||
|
||||
const SERVER_NAME = 'NightSpire'
|
||||
|
||||
/** Raíz del sitio, sin barra final. De aquí salen todos los enlaces e imágenes. */
|
||||
@@ -234,7 +236,8 @@ export function activationEmailHtml(email: string, link: string, locale = 'es'):
|
||||
),
|
||||
pData(`Cuenta: ${esc(email)}<br><br>`),
|
||||
p(
|
||||
'Recuerda que se entra tanto a la web como al servidor con tu correo y tu contraseña.<br>Recuerda que puedes crear las cuentas que quieras desde panel de usuario asociadas a este correo.',
|
||||
'Recuerda que se entra tanto a la web como al servidor con tu correo y tu contraseña.<br>' +
|
||||
`Recuerda que puedes crear hasta ${MAX_GAME_ACCOUNTS} cuentas que quieras desde panel de usuario asociadas a este correo.`,
|
||||
),
|
||||
button(link, 'ACTIVAR LA CUENTA'),
|
||||
fallbackLink(link),
|
||||
|
||||
@@ -95,6 +95,21 @@ export async function activateAccount(hash: string, lastIp = '0.0.0.0'): Promise
|
||||
const emailNorm = normalizeEmail(act.email)
|
||||
const password: string = act.password
|
||||
|
||||
// Volver a comprobar que el correo sigue libre. `registerAccount` ya lo mira, pero
|
||||
// entre registrarse y activar puede haberse creado la cuenta (otra activación
|
||||
// pendiente del mismo correo, un cambio de correo confirmado...), y sin esto se
|
||||
// insertaba una bnet duplicada: `battlenet_accounts.email` no tiene índice único.
|
||||
const [dup] = await db(DB.auth).query<RowDataPacket[]>(
|
||||
'SELECT COUNT(*) AS n FROM battlenet_accounts WHERE email = ?',
|
||||
[emailNorm],
|
||||
)
|
||||
if (Number(dup[0]?.n ?? 0) > 0) {
|
||||
// La activación ya no sirve para nada: la cuenta existe. Se borra para que el
|
||||
// enlace no quede dando vueltas.
|
||||
await db(DB.default).query('DELETE FROM accountactivation WHERE id = ?', [act.id])
|
||||
return { success: false, error: 'emailExists' }
|
||||
}
|
||||
|
||||
// Cuenta Battle.net (SRP6 v2)
|
||||
const bnet = bnetMakeRegistration(emailNorm, password)
|
||||
const [bnetRes] = await db(DB.auth).query<ResultSetHeader>(
|
||||
|
||||
@@ -206,6 +206,7 @@
|
||||
"canPlay": "You can now log in to the website or start enjoying the server.",
|
||||
"invalidLink": "The activation link is invalid",
|
||||
"expiredLink": "The activation link has expired",
|
||||
"emailExists": "An account with that email already exists",
|
||||
"needHelp": "If you need help creating an account, you can contact the {realm} team.",
|
||||
"title": "Account activation"
|
||||
},
|
||||
|
||||
@@ -206,6 +206,7 @@
|
||||
"canPlay": "Ya puedes conectar a la página web o empezar a disfrutar del servidor.",
|
||||
"invalidLink": "El enlace de activación es inválido",
|
||||
"expiredLink": "El enlace de activación ha caducado",
|
||||
"emailExists": "Ya existe una cuenta con ese correo",
|
||||
"needHelp": "Si necesitas ayuda para crear una cuenta, puedes contactar con el equipo de {realm}.",
|
||||
"title": "Activación de cuenta"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user