diff --git a/services/mam-api/src/index.js b/services/mam-api/src/index.js index edec2cc..2adf7b8 100644 --- a/services/mam-api/src/index.js +++ b/services/mam-api/src/index.js @@ -46,7 +46,10 @@ app.use(cors({ // 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)); + // Reject cleanly: omit the Allow-Origin header so the browser surfaces + // a real CORS error instead of a 500 from a thrown Error in the callback. + console.warn('[cors] rejected origin:', origin); + return cb(null, false); }, credentials: true, })); @@ -55,9 +58,17 @@ 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); +// Hard-fail when production-mode auth has no stable session secret. Without +// this, express-session falls back to an in-memory random secret which +// invalidates every session on restart and breaks multi-node deployments. +if (process.env.AUTH_ENABLED === 'true' && !process.env.SESSION_SECRET) { + console.error('[fatal] SESSION_SECRET is required when AUTH_ENABLED=true'); + process.exit(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 }), + store: new PgStore({ pool, tableName: 'sessions', pruneSessionInterval: 60 * 15 /* seconds = 15 min */ }), secret: process.env.SESSION_SECRET, name: 'dragonflight.sid', cookie: {