From c255724ebf8385f6d8c9005dec130845865f441a Mon Sep 17 00:00:00 2001 From: adevopg Date: Wed, 15 Jul 2026 18:53:09 +0000 Subject: [PATCH] =?UTF-8?q?Aceptar=20el=20correo=20en=20MAY=C3=9ASCULAS=20?= =?UTF-8?q?y=20mostrar=20las=20cuentas=20como=20WOW1,=20no=2017#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dos fallos que se juntaban justo en «recuperar nombre de cuenta»: - La validación de Gmail era sensible a mayúsculas (`/@gmail\.com$/` sin la `i`), así que NOVAWOW86@GMAIL.COM se rechazaba con invalidEmail. Y desde que los correos muestran la cuenta en MAYÚSCULAS, quien la copiara de ahí y la pegara no podía recuperar la cuenta, ni registrarse, ni cambiar el correo: la misma regex estaba repetida en register/change-email/recover. Ahora es una sola, con la `i`, en lib/bnet.ts (junto a normalizeEmail). El dominio de un correo NO distingue mayúsculas. - Los correos enseñaban el username interno («17#1»), que no le dice nada a nadie, en vez del nombre visible («WOW1»). Afectaba a DOS plantillas: la de nombres de cuenta y la de la lista de tokens. La conversión existía, pero como helper local de my-account; sube a lib/bnet.ts como `gameAccountDisplayName` (el inverso de `makeGameAccountUsername`) y la aplican las propias plantillas, para que ningún sitio que las use pueda olvidarse. Verificado: la regex acepta las 3 variantes de caja y sigue rechazando no-Gmail; los dos correos renderizados muestran «Cuenta: WOW1 / WOW2». Co-Authored-By: Claude Opus 4.8 (1M context) --- web-next/app/[locale]/my-account/page.tsx | 10 ++-------- web-next/lib/bnet.ts | 18 ++++++++++++++++++ web-next/lib/change-email.ts | 3 +-- web-next/lib/emails.ts | 9 ++++++--- web-next/lib/recover.ts | 3 +-- web-next/lib/register.ts | 3 +-- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/web-next/app/[locale]/my-account/page.tsx b/web-next/app/[locale]/my-account/page.tsx index 9b5be78..28abe7a 100644 --- a/web-next/app/[locale]/my-account/page.tsx +++ b/web-next/app/[locale]/my-account/page.tsx @@ -2,6 +2,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { getRealmName, realmSlug } from '@/lib/realm' +import { gameAccountDisplayName } from '@/lib/bnet' import { getAccountDashboard, getAccountRank } from '@/lib/account' import { reconcileSumUpCheckouts } from '@/lib/sumup' import { getGameAccounts } from '@/lib/auth' @@ -22,13 +23,6 @@ function maskEmail(e: string): string { return `${visible}***@${domain}` } -// Nombre visible de la cuenta de juego: «15#1» → «WOW1» (el nº tras # es la -// cuenta de juego; con varias serían WOW1, WOW2, WOW3…). -function wowAccountName(username: string): string { - const i = username.indexOf('#') - return i >= 0 ? `WOW${username.slice(i + 1)}` : username -} - const ICONS = '/ns-themes/ns-ryu/ns-images/ns-icons' export default async function AccountPage({ params }: { params: Promise<{ locale: string }> }) { @@ -67,7 +61,7 @@ export default async function AccountPage({ params }: { params: Promise<{ locale

{t('regMail')}: {maskEmail(data.regMail)}

diff --git a/web-next/lib/bnet.ts b/web-next/lib/bnet.ts index 1f90539..802e187 100644 --- a/web-next/lib/bnet.ts +++ b/web-next/lib/bnet.ts @@ -147,6 +147,24 @@ export function makeGameAccountUsername(bnetId: number, index = 1): string { return `${bnetId}#${index}` } +/** + * Inverso de `makeGameAccountUsername` para enseñárselo al usuario: «15#1» → «WOW1». + * El número tras # es la cuenta de juego (WOW1…WOW8). El `15#1` es interno y no le + * dice nada a nadie. + */ +export function gameAccountDisplayName(username: string): string { + const i = username.indexOf('#') + return i >= 0 ? `WOW${username.slice(i + 1)}` : username +} + +/** + * Solo se admiten cuentas de Gmail. Lleva la `i` a propósito: sin ella + * `NOVAWOW86@GMAIL.COM` se rechazaba, y es justo como sale el correo en los + * mensajes que enviamos, así que quien lo copiara de ahí no podía ni registrarse + * ni recuperar la cuenta. El dominio del correo NO distingue mayúsculas. + */ +export const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/i + /** * 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 diff --git a/web-next/lib/change-email.ts b/web-next/lib/change-email.ts index 5268684..c39d30c 100644 --- a/web-next/lib/change-email.ts +++ b/web-next/lib/change-email.ts @@ -2,13 +2,12 @@ import crypto from 'node:crypto' import type { RowDataPacket, ResultSetHeader } from 'mysql2' import { db, DB } from './db' import { authenticate } from './auth' -import { bnetMakeRegistration, normalizeEmail } from './bnet' +import { bnetMakeRegistration, normalizeEmail, GMAIL_RE } from './bnet' import { checkSecurityToken } from './security-token' import { sendMail } from './mail' import { confirmNewEmailHtml, confirmOldEmailHtml } from './emails' import type { SessionData } from './session' -const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/ export interface Result { success: boolean diff --git a/web-next/lib/emails.ts b/web-next/lib/emails.ts index 01c453c..ea4ef43 100644 --- a/web-next/lib/emails.ts +++ b/web-next/lib/emails.ts @@ -21,7 +21,7 @@ * entra con el correo, no con un usuario). */ -import { MAX_GAME_ACCOUNTS } from './bnet' +import { MAX_GAME_ACCOUNTS, gameAccountDisplayName } from './bnet' const SERVER_NAME = 'NightSpire' @@ -275,9 +275,10 @@ export function passwordResetEmailHtml(email: string, link: string, locale = 'es } /** `account_names.html` — recordatorio de cuentas de juego. */ +/** `accounts` son los usernames internos («17#1»); aquí se muestran como «WOW1». */ export function accountNamesEmailHtml(email: string, accounts: string[], locale = 'es'): string { const list = accounts.length - ? pData(accounts.map((a) => `Cuenta: ${esc(a)}`).join('
') + '

') + ? pData(accounts.map((a) => `Cuenta: ${esc(gameAccountDisplayName(a))}`).join('
') + '

') : pData('No hay cuentas de juego asociadas todavía.

') return layout('CUENTAS DE JUEGO', 'Tus Cuentas de Juego', [ p( @@ -343,7 +344,9 @@ export function securityTokensListEmailHtml( [ p(`Estos son los Token de seguridad de las cuentas de juego asociadas a ${esc(email)}.

Tus datos de cuenta son:`), pData( - items.map((i) => `Cuenta: ${esc(i.username)}
Token de Seguridad: ${esc(i.token)}`).join('

') + + items + .map((i) => `Cuenta: ${esc(gameAccountDisplayName(i.username))}
Token de Seguridad: ${esc(i.token)}`) + .join('

') + '

', ), p( diff --git a/web-next/lib/recover.ts b/web-next/lib/recover.ts index 19ef89b..32e8c97 100644 --- a/web-next/lib/recover.ts +++ b/web-next/lib/recover.ts @@ -1,7 +1,7 @@ import crypto from 'node:crypto' import type { RowDataPacket } from 'mysql2' import { db, DB } from './db' -import { normalizeEmail, bnetMakeRegistration } from './bnet' +import { normalizeEmail, bnetMakeRegistration, GMAIL_RE } from './bnet' import { sendMail } from './mail' import { activationEmailHtml, @@ -10,7 +10,6 @@ import { securityTokensListEmailHtml, } from './emails' -const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/ export interface Result { success: boolean diff --git a/web-next/lib/register.ts b/web-next/lib/register.ts index ca81e5f..ae1d12b 100644 --- a/web-next/lib/register.ts +++ b/web-next/lib/register.ts @@ -1,11 +1,10 @@ import type { ResultSetHeader, RowDataPacket } from 'mysql2' import { db, DB } from './db' -import { normalizeEmail, bnetMakeRegistration, gameMakeRegistration, makeGameAccountUsername } from './bnet' +import { normalizeEmail, bnetMakeRegistration, gameMakeRegistration, makeGameAccountUsername, GMAIL_RE } from './bnet' import { sendMail } from './mail' import { activationEmailHtml } from './emails' import { randomToken } from './random-token' -const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/ export interface RegisterInput { password: string