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}:
`),
)
}
} 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' }
}