refactor: implement centralized auth middleware and standardize cross-subdomain session management
This commit is contained in:
@@ -1,101 +1,166 @@
|
||||
const query = require('../storage/database').query;
|
||||
const track = require('../tools/tracking');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const { query } = require('../storage/database');
|
||||
const security = require('../tools/security');
|
||||
const tracking = require('../tools/tracking');
|
||||
|
||||
async function register(username, password) {
|
||||
const userExists = await query('SELECT id FROM users WHERE username = $1', [username]);
|
||||
// ─── ERRORI CUSTOM ──────────────────────────────────────────────────
|
||||
|
||||
if (userExists.rows.length > 0) {
|
||||
throw new Error('User already exists');
|
||||
class AuthError extends Error {
|
||||
constructor(code, message) {
|
||||
super(message || code);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
const hashedPassword = await security.hashPassword(password);
|
||||
const id = uuid();
|
||||
|
||||
await query(
|
||||
'INSERT INTO users (id, username, password_hash) VALUES ($1, $2, $3)',
|
||||
[id, username, hashedPassword]
|
||||
);
|
||||
|
||||
return { success: true, user: { id, username } };
|
||||
}
|
||||
|
||||
// ─── 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 result = await query(
|
||||
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');
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('No user matched');
|
||||
}
|
||||
const user = rows[0];
|
||||
if (!user.is_active) throw new AuthError('ACCOUNT_INACTIVE', 'Account disattivato');
|
||||
|
||||
const user = result.rows[0];
|
||||
const ok = await security.verifyPassword(password, user.password_hash);
|
||||
if (!ok) throw new AuthError('INVALID_CREDENTIALS', 'Credenziali non valide');
|
||||
|
||||
if (!user.is_active) {
|
||||
throw new Error('User account is not active');
|
||||
}
|
||||
|
||||
const isValid = await security.verifyPassword(password, user.password_hash);
|
||||
|
||||
if (!isValid) {
|
||||
throw new Error('Password mismatch');
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
created: user.created_at
|
||||
};
|
||||
return { id: user.id, username: user.username, created_at: user.created_at };
|
||||
}
|
||||
|
||||
async function logout(sessionID) {
|
||||
if (!sessionID) {
|
||||
throw new Error('No session ID provided');
|
||||
}
|
||||
// ─── SESSIONI ───────────────────────────────────────────────────────
|
||||
|
||||
const result = await query('UPDATE sessions SET is_revoked = TRUE WHERE id = $1', [sessionID]);
|
||||
return result.rowCount > 0;
|
||||
}
|
||||
|
||||
async function newSession(userId, userAgent, ip) {
|
||||
async function createSession(userId, userAgent, ip) {
|
||||
const id = uuid();
|
||||
const sessionCode = security.generateSessionCode();
|
||||
const metadata = track.getBasicMetadata(userAgent);
|
||||
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)
|
||||
`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, sessionCode, '', ip, userAgent, metadata.browser, metadata.os, metadata.device_type]
|
||||
[id, userId, code, '', ip, userAgent, meta.browser, meta.os, meta.device_type]
|
||||
);
|
||||
|
||||
return { id, sessionCode };
|
||||
return { id, code };
|
||||
}
|
||||
|
||||
async function validateSession(sessionId) {
|
||||
if (!sessionId || typeof sessionId !== 'string') {
|
||||
throw new Error('Invalid session ID');
|
||||
throw new AuthError('INVALID_SESSION', 'Sessione non valida');
|
||||
}
|
||||
|
||||
const result = await query(
|
||||
'SELECT s.id, u.is_active FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.id = $1 AND s.is_revoked = FALSE',
|
||||
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 (result.rows.length === 0) {
|
||||
throw new Error('Session not found or revoked');
|
||||
}
|
||||
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');
|
||||
|
||||
if (!result.rows[0].is_active) {
|
||||
throw new Error('User account is not active');
|
||||
// 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,
|
||||
logout,
|
||||
newSession,
|
||||
validateSession
|
||||
createSession,
|
||||
validateSession,
|
||||
revokeSession,
|
||||
listSessions,
|
||||
getUserById,
|
||||
getAllUsers,
|
||||
getUsersToNotify,
|
||||
updateUsername,
|
||||
updateTelegram
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user