import session from "express-session"; import type { Sequelize } from "sequelize"; import { QueryTypes } from "sequelize"; import { restoreSession, sessionRow, writeSession } from "./session-normalization"; type Callback = (error?: Error | null) => void; function failure(error: unknown) { return error instanceof Error ? error : new Error("Fallo del almacén de sesiones."); } /** Almacén propio: persistencia relacional del contrato de sesión usado por Metis Desk. */ export class SequelizeSessionStore extends session.Store { constructor(private readonly database: Sequelize) { super(); } async get( sid: string, callback: (error: Error | null, data?: session.SessionData | null) => void ) { let result: session.SessionData | null = null; let error: Error | null = null; try { const rows = await this.database.query>( "SELECT * FROM http_sessions WHERE sid=:sid AND expires>NOW()", { replacements: { sid }, type: QueryTypes.SELECT } ); result = rows[0] ? restoreSession(rows[0]) : null; } catch (caught) { error = failure(caught); } callback(error, result); } set(sid: string, data: session.SessionData, callback?: Callback) { return this.complete(async () => { await writeSession(this.database, sid, data); await this.database.query("DELETE FROM http_sessions WHERE expires<=NOW()"); }, callback); } destroy(sid: string, callback?: Callback) { return this.complete(async () => { await this.database.query("DELETE FROM http_sessions WHERE sid=:sid", { replacements: { sid }, }); }, callback); } touch(sid: string, data: session.SessionData, callback?: Callback) { return this.complete(async () => { const cookie = sessionRow(data); const expires = cookie.cookieExpires ? new Date(cookie.cookieExpires) : new Date(Date.now() + 8 * 60 * 60 * 1000); // Tocar la sesión renueva la cookie; no sobrescribe la autenticación de otra petición. await this.database.query( "UPDATE http_sessions SET expires=:expires,cookieExpires=:cookieExpires,updatedAt=NOW() WHERE sid=:sid", { replacements: { sid, expires, cookieExpires: cookie.cookieExpires } } ); }, callback); } private async complete(operation: () => Promise, callback?: Callback) { let error: Error | null = null; try { await operation(); } catch (caught) { if (!callback) { throw caught; } error = failure(caught); } callback?.(error); } }