diff --git a/web-next/app/[locale]/activate-account/ActivateClient.tsx b/web-next/app/[locale]/activate-account/ActivateClient.tsx
index 098f4d8..41a222c 100644
--- a/web-next/app/[locale]/activate-account/ActivateClient.tsx
+++ b/web-next/app/[locale]/activate-account/ActivateClient.tsx
@@ -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')
diff --git a/web-next/app/api/account/add-game-account/route.ts b/web-next/app/api/account/add-game-account/route.ts
index 307987a..bdf412c 100644
--- a/web-next/app/api/account/add-game-account/route.ts
+++ b/web-next/app/api/account/add-game-account/route.ts
@@ -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.
diff --git a/web-next/lib/bnet.ts b/web-next/lib/bnet.ts
index 89d0673..1f90539 100644
--- a/web-next/lib/bnet.ts
+++ b/web-next/lib/bnet.ts
@@ -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…).
diff --git a/web-next/lib/emails.ts b/web-next/lib/emails.ts
index 710cc8c..01c453c 100644
--- a/web-next/lib/emails.ts
+++ b/web-next/lib/emails.ts
@@ -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)}
`),
p(
- 'Recuerda que se entra tanto a la web como al servidor con tu correo y tu contraseña.
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.
' +
+ `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),
diff --git a/web-next/lib/register.ts b/web-next/lib/register.ts
index 6b96dae..553314d 100644
--- a/web-next/lib/register.ts
+++ b/web-next/lib/register.ts
@@ -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(
+ '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(
diff --git a/web-next/messages/en.json b/web-next/messages/en.json
index 1fde22d..bfc6c82 100644
--- a/web-next/messages/en.json
+++ b/web-next/messages/en.json
@@ -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"
},
diff --git a/web-next/messages/es.json b/web-next/messages/es.json
index 9103705..9f24c51 100644
--- a/web-next/messages/es.json
+++ b/web-next/messages/es.json
@@ -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"
},