From a094df03eacf9aa07f31021a52f42eb5540e6898 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Wed, 27 May 2026 14:06:41 -0400 Subject: [PATCH] feat(mam-api): wire express-session + tighten CORS allowlist --- services/mam-api/src/index.js | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/services/mam-api/src/index.js b/services/mam-api/src/index.js index f49d9e5..edec2cc 100644 --- a/services/mam-api/src/index.js +++ b/services/mam-api/src/index.js @@ -1,6 +1,9 @@ import 'dotenv/config'; import express from 'express'; import cors from 'cors'; +import session from 'express-session'; +import connectPgSimple from 'connect-pg-simple'; +const PgStore = connectPgSimple(session); import os from 'node:os'; import { exec } from 'node:child_process'; import pool from './db/pool.js'; @@ -34,9 +37,41 @@ const app = express(); const PORT = process.env.PORT || 3000; // ── Middleware ──────────────────────────────────────────────────────────────── -app.use(cors({ origin: true, credentials: true })); +// Tightened CORS — once cookies carry authority, `origin: true` would let +// any site forge requests with the cookie. Drive the allowlist from env. +const allowedOrigins = (process.env.ALLOWED_ORIGINS || '') + .split(',').map(s => s.trim()).filter(Boolean); +app.use(cors({ + origin: (origin, cb) => { + // No Origin header (same-origin or curl) — allow. + if (!origin) return cb(null, true); + if (allowedOrigins.length === 0 || allowedOrigins.includes(origin)) return cb(null, true); + return cb(new Error('CORS: origin not allowed: ' + origin)); + }, + credentials: true, +})); app.use(express.json({ limit: '50mb' })); +// Trust the reverse proxy only when explicitly told to (production HTTPS). +if (process.env.TRUST_PROXY === 'true') app.set('trust proxy', 1); + +// Session — actually wired this time. See specs/2026-05-27-auth-system-design.md. +app.use(session({ + store: new PgStore({ pool, tableName: 'sessions', pruneSessionInterval: 60 * 15 }), + secret: process.env.SESSION_SECRET, + name: 'dragonflight.sid', + cookie: { + httpOnly: true, + sameSite: 'lax', + secure: process.env.TRUST_PROXY === 'true', + path: '/', + maxAge: 8 * 3600 * 1000, + }, + rolling: false, // sliding renewal handled in requireAuth so idle + absolute can be enforced separately + resave: false, + saveUninitialized: false, +})); + // ── Health ──────────────────────────────────────────────────────────────────── app.get('/health', (_req, res) => res.json({ status: 'ok' }));