fix(mam-api): SESSION_SECRET boot guard + cleaner CORS rejection
Code-review feedback: - Hard-fail boot when AUTH_ENABLED=true and SESSION_SECRET is unset, so express-session can't silently use an in-memory random secret that invalidates sessions on restart and breaks multi-node clusters. - CORS rejection now returns cb(null, false) instead of cb(new Error) so misconfigured origins surface as clean CORS errors in the browser instead of HTTP 500s. Log a warn line for operator visibility. - pruneSessionInterval units comment.
This commit is contained in:
parent
a094df03ea
commit
88c3aa5149
1 changed files with 13 additions and 2 deletions
|
|
@ -46,7 +46,10 @@ app.use(cors({
|
||||||
// No Origin header (same-origin or curl) — allow.
|
// No Origin header (same-origin or curl) — allow.
|
||||||
if (!origin) return cb(null, true);
|
if (!origin) return cb(null, true);
|
||||||
if (allowedOrigins.length === 0 || allowedOrigins.includes(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,
|
credentials: true,
|
||||||
}));
|
}));
|
||||||
|
|
@ -55,9 +58,17 @@ app.use(express.json({ limit: '50mb' }));
|
||||||
// Trust the reverse proxy only when explicitly told to (production HTTPS).
|
// Trust the reverse proxy only when explicitly told to (production HTTPS).
|
||||||
if (process.env.TRUST_PROXY === 'true') app.set('trust proxy', 1);
|
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.
|
// Session — actually wired this time. See specs/2026-05-27-auth-system-design.md.
|
||||||
app.use(session({
|
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,
|
secret: process.env.SESSION_SECRET,
|
||||||
name: 'dragonflight.sid',
|
name: 'dragonflight.sid',
|
||||||
cookie: {
|
cookie: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue