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 type { SessionData } from './session' const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/ export interface Result { success: boolean error?: string } function shell(title: string, body: string): string { return `

Nova WoW

${title}

${body}
` } export async function requestEmailChange( session: SessionData, curPassword: string, curEmail: string, newEmail: string, confEmail: string, token: string, ): Promise { 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 home_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`, shell('Confirma el cambio de correo', `

Has solicitado cambiar tu correo a ${newEmail}. Confirma desde tu correo actual:

Confirmar

${link}

`), ) return { success: true } } export async function confirmOldEmail(hash: string): Promise { const [rows] = await db(DB.default).query( 'SELECT id, email, hash FROM home_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 home_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`, shell('Confirma tu nuevo correo', `

Confirma este correo como el nuevo de tu cuenta:

Confirmar nuevo correo

${link}

`), ) return { success: true } } export async function confirmNewEmail(hash: string): Promise { const [rows] = await db(DB.default).query( 'SELECT id, email, old_email, password FROM home_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 home_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( '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( '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 home_accountactivation WHERE id = ?', [act.id]) return { success: true } }