feat(identity): add persistent Postgres-backed session store
Replaces the in-memory studentSessions/adminSessions Maps (Phase 2B — gone on every restart) with a sessions table. Browser holds an opaque CSPRNG bearer token; only its SHA-256 hash is ever stored server-side, same principle as password hashing. Each session has an absolute expiry (fixed at creation) and a sliding idle expiry (pushed forward on every authenticated request, capped at the absolute one) — one atomic UPDATE...RETURNING per resolve, no read-then-write race. Student and instructor sessions stay fully separate token namespaces, matching Phase 2B's cookie-name separation. cookies.js centralizes Set-Cookie construction so the Secure attribute (COOKIE_SECURE=1, for real HTTPS deployments) is applied consistently instead of hand-built per call site.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
// Cookie construction, centralized so the Secure attribute is set consistently everywhere
|
||||
// instead of hand-built per call site. Secure is opt-in via COOKIE_SECURE=1 — off by default
|
||||
// because local dev and the current single-VM pilot deployment run over plain HTTP; anything
|
||||
// served over real HTTPS should set COOKIE_SECURE=1 (see docs/PHASE3A... "production HTTPS
|
||||
// expectations").
|
||||
|
||||
const SECURE = process.env.COOKIE_SECURE === '1';
|
||||
|
||||
function buildCookie(name, value, { maxAgeSec, httpOnly = true } = {}) {
|
||||
const parts = [`${name}=${value}`, 'Path=/', 'SameSite=Lax'];
|
||||
if (httpOnly) parts.push('HttpOnly');
|
||||
if (SECURE) parts.push('Secure');
|
||||
parts.push(`Max-Age=${maxAgeSec != null ? maxAgeSec : 0}`);
|
||||
return parts.join('; ');
|
||||
}
|
||||
|
||||
function clearCookie(name) {
|
||||
return buildCookie(name, '', { maxAgeSec: 0 });
|
||||
}
|
||||
|
||||
module.exports = { buildCookie, clearCookie };
|
||||
Reference in New Issue
Block a user