const { v4: uuid } = require('uuid'); const { query } = require('../storage/database'); const security = require('../tools/security'); const tracking = require('../tools/tracking'); // ─── ERRORI CUSTOM ────────────────────────────────────────────────── class AuthError extends Error { constructor(code, message) { super(message || code); this.code = code; } } // ─── REGISTRAZIONE ────────────────────────────────────────────────── async function register(username, password) { const exists = await query('SELECT id FROM users WHERE username = $1', [username]); if (exists.rows.length) throw new AuthError('USER_EXISTS', 'Username già in uso'); const hash = await security.hashPassword(password); const id = uuid(); await query( 'INSERT INTO users (id, username, password_hash) VALUES ($1, $2, $3)', [id, username, hash] ); return { id, username }; } // ─── LOGIN ────────────────────────────────────────────────────────── async function login(username, password) { const { rows } = await query( 'SELECT id, username, password_hash, is_active, created_at FROM users WHERE username = $1', [username] ); if (!rows.length) throw new AuthError('INVALID_CREDENTIALS', 'Credenziali non valide'); const user = rows[0]; if (!user.is_active) throw new AuthError('ACCOUNT_INACTIVE', 'Account disattivato'); const ok = await security.verifyPassword(password, user.password_hash); if (!ok) throw new AuthError('INVALID_CREDENTIALS', 'Credenziali non valide'); return { id: user.id, username: user.username, created_at: user.created_at }; } // ─── SESSIONI ─────────────────────────────────────────────────────── async function createSession(userId, userAgent, ip) { const id = uuid(); const code = security.sessionCode(); const meta = tracking.extract(userAgent); await query( `INSERT INTO sessions (id, user_id, session_code, encoded_username, ip_address, user_agent, browser, os, device_type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [id, userId, code, '', ip, userAgent, meta.browser, meta.os, meta.device_type] ); return { id, code }; } async function validateSession(sessionId) { if (!sessionId || typeof sessionId !== 'string') { throw new AuthError('INVALID_SESSION', 'Sessione non valida'); } const { rows } = await query( `SELECT s.id, s.is_revoked, u.is_active FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.id = $1`, [sessionId] ); if (!rows.length) throw new AuthError('INVALID_SESSION', 'Sessione non trovata'); if (rows[0].is_revoked) throw new AuthError('SESSION_REVOKED', 'Sessione revocata'); if (!rows[0].is_active) throw new AuthError('ACCOUNT_INACTIVE', 'Account disattivato'); // Aggiorna last_active in modo non bloccante query('UPDATE sessions SET last_active = NOW() WHERE id = $1', [sessionId]).catch(() => {}); return true; } async function revokeSession(sessionId, userId) { if (userId) { const r = await query( 'UPDATE sessions SET is_revoked = TRUE WHERE id = $1 AND user_id = $2 AND is_revoked = FALSE', [sessionId, userId] ); return r.rowCount > 0; } const r = await query( 'UPDATE sessions SET is_revoked = TRUE WHERE id = $1 AND is_revoked = FALSE', [sessionId] ); return r.rowCount > 0; } async function listSessions(userId) { const { rows } = await query( `SELECT id, ip_address, browser, os, device_type, location_country, location_city, created_at, last_active, is_revoked FROM sessions WHERE user_id = $1 ORDER BY last_active DESC`, [userId] ); return rows; } // ─── LOOKUP UTENTE ────────────────────────────────────────────────── async function getUserById(userId) { const { rows } = await query( 'SELECT id, username, is_active, created_at, telegram_id FROM users WHERE id = $1', [userId] ); return rows[0] || null; } async function getAllUsers() { const { rows } = await query( 'SELECT id, username, is_active, created_at, telegram_id FROM users' ); return rows; } async function getUsersToNotify() { const { rows } = await query( 'SELECT telegram_id FROM users WHERE telegram_id IS NOT NULL' ); return rows; } async function updateUsername(userId, newUsername) { const r = await query( 'UPDATE users SET username = $1 WHERE id = $2 RETURNING username', [newUsername, userId] ); return r.rowCount > 0 ? r.rows[0] : null; } async function updateTelegram(userId, telegramId) { await query( 'UPDATE users SET telegram_id = $1 WHERE id = $2', [telegramId, userId] ); } module.exports = { AuthError, register, login, createSession, validateSession, revokeSession, listSessions, getUserById, getAllUsers, getUsersToNotify, updateUsername, updateTelegram };