import crypto from 'node:crypto' import type { RowDataPacket } from 'mysql2' import { db, DB } from './db' import { normalizeEmail, bnetMakeRegistration } from './bnet' import { sendMail } from './mail' const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/ export interface Result { success: boolean error?: string redirect?: string } function emailShell(title: string, bodyHtml: string): string { return `

Nova WoW

${title}

${bodyHtml}
` } /** Recuperación por email (anti-enumeración: respuesta genérica siempre). */ export async function requestRecovery(type: string, email: string): Promise { email = (email || '').trim() if (!email || !GMAIL_RE.test(email)) return { success: false, error: 'invalidEmail' } const site = process.env.SITE_URL || '' if (type === 'password') { try { const [rows] = await db(DB.auth).query( 'SELECT id FROM battlenet_accounts WHERE email = ?', [normalizeEmail(email)], ) if (rows[0]) { const token = crypto.randomBytes(32).toString('hex') await db(DB.default).query( 'INSERT INTO home_passwordreset (email, token, created_at, used) VALUES (?, ?, NOW(), 0)', [email, token], ) const link = `${site}/reset-password?token=${token}` await sendMail( email, 'Restablecer contraseña - Nova WoW', emailShell( 'Restablecer contraseña', `

Pulsa para elegir una nueva contraseña (válido 1 hora):

Restablecer

${link}

`, ), ) } } catch { /* acore no disponible: respuesta genérica igualmente */ } return { success: true } } if (type === 'accountname') { try { const [acc] = await db(DB.auth).query( 'SELECT id FROM battlenet_accounts WHERE email = ?', [normalizeEmail(email)], ) if (acc[0]) { const [games] = await db(DB.auth).query( 'SELECT username FROM account WHERE battlenet_account = ? ORDER BY battlenet_index', [acc[0].id], ) const list = games.map((g) => `
  • ${g.username}
  • `).join('') || '
  • ' await sendMail( email, 'Tus cuentas de juego - Nova WoW', emailShell('Tus cuentas de juego', `

    Cuentas asociadas a ${email}:

      ${list}
    `), ) } } catch { /* ignore */ } return { success: true } } if (type === 'activation') { try { const [rows] = await db(DB.default).query( 'SELECT hash, password FROM home_accountactivation WHERE email = ? AND old_email IS NULL ORDER BY created_at DESC LIMIT 1', [email], ) if (rows[0]) { const link = `${site}/activate-account?act=${rows[0].hash}` await sendMail( email, `Activación de la cuenta ${email} - Nova WoW`, emailShell('Activa tu cuenta', `

    Activar cuenta

    ${link}

    `), ) } } catch { /* ignore */ } return { success: true } } return { success: false, error: 'invalidType' } } export async function resetPassword(token: string, newPassword: string, confPassword: string): Promise { const [rows] = await db(DB.default).query( 'SELECT id, email, created_at FROM home_passwordreset WHERE token = ? AND used = 0', [token], ) const pr = rows[0] if (!pr) return { success: false, error: 'invalidLink' } if (Date.now() - new Date(pr.created_at).getTime() > 3600_000) return { success: false, error: 'expiredLink' } if (!newPassword || newPassword !== confPassword) return { success: false, error: 'passwordMismatch' } if (newPassword.length > 16) return { success: false, error: 'passwordTooLong' } try { const reg = bnetMakeRegistration(pr.email, newPassword) const [res] = await db(DB.auth).query( 'UPDATE battlenet_accounts SET srp_version = ?, salt = ?, verifier = ? WHERE email = ?', [reg.srpVersion, reg.salt, reg.verifier, normalizeEmail(pr.email)], ) // @ts-expect-error affectedRows if (!res.affectedRows) return { success: false, error: 'accountNotFound' } } catch { return { success: false, error: 'genericError' } } await db(DB.default).query('UPDATE home_passwordreset SET used = 1 WHERE id = ?', [pr.id]) return { success: true, redirect: '/login' } }