Files
NightSpire/web-next/lib/change-email.ts
T
Inna cccc70338d Quitar el prefijo home_ de las 41 tablas del portal
El prefijo venía del nombre de la app Django (`home`), jubilada y borrada hoy,
así que ya no significaba nada: home_stripelog -> stripelog. La BD se sigue
llamando django_wow por historia (renombrarla es otra operación).

Comprobado antes de tocar nada: 0 colisiones con nombres existentes, 0 palabras
reservadas (contrastado contra las 260 de MySQL), sin vistas ni triggers que
dependieran de ellas. El RENAME va en una sola sentencia porque así es atómico,
y MySQL reapunta solo las 4 claves ajenas entre estas tablas.

170 referencias actualizadas en 37 ficheros. Se dejó a propósito
sql/drop_home_securitytoken_authuser_fk.sql sin tocar: es una migración ya
aplicada y reescribirla sería falsear el historial.

De paso salió un fallo previo: prices.ts consultaba `home_restoreitemprice`,
una tabla que NO existe. El try/catch de priceFrom se comía el error y devolvía
siempre el precio por defecto, así que el precio de restaurar objetos nunca fue
configurable. Se deja documentado en el código; crear la tabla es otra decisión.

Verificado tras aplicar: 0 tablas home_, las 51 siguen ahí, y el conteo exacto
de filas cuadra con el volcado previo (store_item 3068, item_data 44873,
stripelog 35, store_order 26, noticia 50). La web responde 200 en todas las
rutas y la portada carga las noticias sin errores.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:49:19 +00:00

105 lines
4.2 KiB
TypeScript

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 { 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
error?: string
}
export async function requestEmailChange(
session: SessionData,
curPassword: string,
curEmail: string,
newEmail: string,
confEmail: string,
token: string,
): Promise<Result> {
const current = session.bnetEmail ?? '' // ya normalizado (MAYÚS)
const userId = session.accountId ?? 0
if (!curPassword || !curEmail || !newEmail || !confEmail || !token) return { success: false, error: 'missingFields' }
if (normalizeEmail(curEmail) !== current) return { success: false, error: 'wrongCurrentEmail' }
if (newEmail !== confEmail || !GMAIL_RE.test(newEmail)) return { success: false, error: 'invalidEmail' }
if (!(await checkSecurityToken(userId, token))) return { success: false, error: 'invalidToken' }
if (!(await authenticate(current, curPassword))) return { success: false, error: 'wrongCurrentPassword' }
const oldHash = crypto.randomBytes(16).toString('hex')
const newHash = crypto.randomBytes(16).toString('hex')
await db(DB.default).query(
'INSERT INTO accountactivation (username, email, old_email, password, recruiter_id, hash, old_email_hash, is_used, is_new_email_used, created_at) ' +
'VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, NOW())',
[session.username ?? null, newEmail, current, curPassword, userId, newHash, oldHash],
)
const site = process.env.SITE_URL || ''
const link = `${site}/confirm-old-email?hash=${oldHash}`
await sendMail(
current,
`Confirma el cambio de correo - Nova WoW`,
confirmOldEmailHtml(session.username ?? current, newEmail, link),
)
return { success: true }
}
export async function confirmOldEmail(hash: string): Promise<Result> {
const [rows] = await db(DB.default).query<RowDataPacket[]>(
'SELECT id, email, hash, username FROM accountactivation WHERE old_email_hash = ? AND is_used = 0',
[hash],
)
const act = rows[0]
if (!act) return { success: false, error: 'invalidLink' }
await db(DB.default).query('UPDATE accountactivation SET is_used = 1 WHERE id = ?', [act.id])
const site = process.env.SITE_URL || ''
const link = `${site}/confirm-new-email?hash=${act.hash}`
await sendMail(
act.email,
`Confirma tu nuevo correo - Nova WoW`,
confirmNewEmailHtml(act.username ?? act.email, link),
)
return { success: true }
}
export async function confirmNewEmail(hash: string): Promise<Result> {
const [rows] = await db(DB.default).query<RowDataPacket[]>(
'SELECT id, email, old_email, password FROM accountactivation WHERE hash = ? AND is_new_email_used = 0 AND old_email IS NOT NULL',
[hash],
)
const act = rows[0]
if (!act) return { success: false, error: 'invalidLink' }
await db(DB.default).query('UPDATE accountactivation SET is_new_email_used = 1 WHERE id = ?', [act.id])
const newNorm = normalizeEmail(act.email)
const oldNorm = normalizeEmail(act.old_email || '')
try {
const reg = bnetMakeRegistration(newNorm, act.password)
const [bnetRows] = await db(DB.auth).query<RowDataPacket[]>(
'SELECT id FROM battlenet_accounts WHERE email = ?',
[oldNorm],
)
if (!bnetRows[0]) return { success: false, error: 'accountNotFound' }
const bnetId = bnetRows[0].id
await db(DB.auth).query<ResultSetHeader>(
'UPDATE battlenet_accounts SET email = ?, srp_version = ?, salt = ?, verifier = ? WHERE id = ?',
[newNorm, reg.srpVersion, reg.salt, reg.verifier, bnetId],
)
await db(DB.auth).query('UPDATE account SET email = ?, reg_mail = ? WHERE battlenet_account = ?', [
newNorm,
newNorm,
bnetId,
])
} catch {
return { success: false, error: 'genericError' }
}
await db(DB.default).query('DELETE FROM accountactivation WHERE id = ?', [act.id])
return { success: true }
}