167 lines
5.5 KiB
JavaScript
167 lines
5.5 KiB
JavaScript
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
|
|
};
|