customer-presentation/src/sessions.js
2026-04-27 10:49:06 +02:00

51 lines
1.9 KiB
JavaScript

// ─────────────────────────────────────────────────────────────
// src/sessions.js — session cookies.
//
// Sessions: opaque 256-bit random IDs stored in SQLite, not JWTs.
// Revoking a session is a DELETE; no signing keys to rotate.
// ─────────────────────────────────────────────────────────────
import crypto from 'node:crypto';
import { q } from './db.js';
export const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
export const COOKIE_NAME = 'fenja_session';
// ─── Crypto helpers ──────────────────────────────────────────
export function randomSessionId() {
return crypto.randomBytes(32).toString('hex'); // 64 hex chars
}
// ─── Session lifecycle ───────────────────────────────────────
export function issueSession(req, res, email) {
const id = randomSessionId();
const now = Date.now();
q.createSession.run(
id,
email,
now,
now + SESSION_TTL_MS,
req.ip || null,
req.get('user-agent')?.slice(0, 500) || null
);
res.cookie(COOKIE_NAME, id, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: SESSION_TTL_MS,
});
return id;
}
export function clearSession(req, res) {
const id = req.cookies?.[COOKIE_NAME];
if (id) q.deleteSession.run(id);
res.clearCookie(COOKIE_NAME, { path: '/' });
}
export function currentSession(req) {
const id = req.cookies?.[COOKIE_NAME];
if (!id) return null;
return q.getSession.get(id, Date.now()) || null;
}