|
|
|
|
@ -1,8 +1,73 @@
|
|
|
|
|
import type { Handle } from '@sveltejs/kit';
|
|
|
|
|
import { getDb } from '$lib/server/db';
|
|
|
|
|
import { sha256Hex } from '$lib/server/crypto';
|
|
|
|
|
import { isProd, sessionIdleTtlMs } from '$lib/server/env';
|
|
|
|
|
|
|
|
|
|
function toIsoSql(d: Date): string {
|
|
|
|
|
return d.toISOString().replace('T', ' ').replace('Z', '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
|
|
|
// Handler mínimo (sin sesión aún). Añadimos cabeceras de seguridad básicas.
|
|
|
|
|
// Sesión por cookie 'sid'
|
|
|
|
|
const sid = event.cookies.get('sid');
|
|
|
|
|
if (sid) {
|
|
|
|
|
try {
|
|
|
|
|
const db = await getDb();
|
|
|
|
|
const hash = await sha256Hex(sid);
|
|
|
|
|
|
|
|
|
|
// Validar sesión vigente
|
|
|
|
|
const row = db
|
|
|
|
|
.prepare(
|
|
|
|
|
`SELECT user_id FROM web_sessions
|
|
|
|
|
WHERE session_hash = ?
|
|
|
|
|
AND expires_at > strftime('%Y-%m-%d %H:%M:%f','now')
|
|
|
|
|
LIMIT 1`
|
|
|
|
|
)
|
|
|
|
|
.get(hash) as { user_id: string } | undefined;
|
|
|
|
|
|
|
|
|
|
if (row?.user_id) {
|
|
|
|
|
event.locals.userId = row.user_id;
|
|
|
|
|
|
|
|
|
|
// Renovar expiración por inactividad y last_seen_at
|
|
|
|
|
const newExpIso = toIsoSql(new Date(Date.now() + sessionIdleTtlMs));
|
|
|
|
|
try {
|
|
|
|
|
db.prepare(
|
|
|
|
|
`UPDATE web_sessions
|
|
|
|
|
SET last_seen_at = strftime('%Y-%m-%d %H:%M:%f','now'),
|
|
|
|
|
expires_at = ?
|
|
|
|
|
WHERE session_hash = ?`
|
|
|
|
|
).run(newExpIso, hash);
|
|
|
|
|
} catch {
|
|
|
|
|
// Si no existe last_seen_at en el esquema, al menos renovar expires_at
|
|
|
|
|
try {
|
|
|
|
|
db.prepare(
|
|
|
|
|
`UPDATE web_sessions
|
|
|
|
|
SET expires_at = ?
|
|
|
|
|
WHERE session_hash = ?`
|
|
|
|
|
).run(newExpIso, hash);
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Refrescar cookie (idle)
|
|
|
|
|
event.cookies.set('sid', sid, {
|
|
|
|
|
path: '/',
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
secure: isProd(),
|
|
|
|
|
maxAge: Math.floor(sessionIdleTtlMs / 1000)
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// Sesión inválida/expirada
|
|
|
|
|
event.cookies.delete('sid', { path: '/' });
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// En caso de error de DB, no romper la request; continuar sin sesión
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await resolve(event);
|
|
|
|
|
|
|
|
|
|
// Cabeceras de seguridad básicas
|
|
|
|
|
try {
|
|
|
|
|
response.headers.set('X-Frame-Options', 'DENY');
|
|
|
|
|
response.headers.set('Referrer-Policy', 'no-referrer');
|
|
|
|
|
|