From 65684aa57780e570a0e8723849c86b614324eab3 Mon Sep 17 00:00:00 2001 From: opencode Date: Wed, 27 May 2026 02:54:25 +0000 Subject: [PATCH] fix(auth): ensure sessions table exists + log session.save errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The redirect loop after successful login was almost certainly the `sessions` table never being created. `schema.sql` defines it but only runs on first-init via the postgres entrypoint; instances bootstrapped via mam-api's own migration loop never got the table. express-session's `req.session.save()` then failed silently and the cookie pointed at a sid that wasn't in the store — every subsequent request looked like a brand-new visitor. - New migration 021-ensure-sessions-table.sql (idempotent). - connect-pg-simple now configured with `createTableIfMissing: true` as belt-and-braces. - `POST /auth/login` now explicitly waits for session.save() and surfaces both regenerate() and save() errors instead of treating them as 'success'. Logs sid + req.secure + req.protocol so we can confirm trust-proxy is doing the right thing behind NPM. --- .../migrations/021-ensure-sessions-table.sql | 14 ++++++++++ services/mam-api/src/index.js | 5 +++- services/mam-api/src/routes/auth.js | 27 ++++++++++++++----- 3 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 services/mam-api/src/db/migrations/021-ensure-sessions-table.sql diff --git a/services/mam-api/src/db/migrations/021-ensure-sessions-table.sql b/services/mam-api/src/db/migrations/021-ensure-sessions-table.sql new file mode 100644 index 0000000..605386e --- /dev/null +++ b/services/mam-api/src/db/migrations/021-ensure-sessions-table.sql @@ -0,0 +1,14 @@ +-- connect-pg-simple's session store needs this table. It's defined in +-- schema.sql which only runs on first DB init via the postgres entrypoint; +-- on instances bootstrapped via migrations only (no entrypoint init), the +-- table never existed and every login silently failed to persist the +-- session — manifesting as a redirect loop after submitting valid creds. +-- Idempotent so this is safe to re-run. + +CREATE TABLE IF NOT EXISTS sessions ( + sid TEXT PRIMARY KEY, + sess JSONB NOT NULL, + expire TIMESTAMPTZ NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_sessions_expire ON sessions (expire); diff --git a/services/mam-api/src/index.js b/services/mam-api/src/index.js index 1188b0d..d275d4a 100644 --- a/services/mam-api/src/index.js +++ b/services/mam-api/src/index.js @@ -76,8 +76,11 @@ app.use( name: 'df.sid', store: new PgSession({ pool, - tableName: 'sessions', + tableName: 'sessions', pruneSessionInterval: 3600, + // Belt-and-braces: connect-pg-simple will CREATE TABLE on its first + // write if migration 021 somehow didn't run. Cheap, idempotent. + createTableIfMissing: true, }), secret: SESSION_SECRET, resave: false, diff --git a/services/mam-api/src/routes/auth.js b/services/mam-api/src/routes/auth.js index 6aabaf4..6ca4885 100644 --- a/services/mam-api/src/routes/auth.js +++ b/services/mam-api/src/routes/auth.js @@ -127,17 +127,30 @@ router.post('/login', async (req, res, next) => { // Successful login — clear any accumulated failed attempts clearAttempts(req, username); - // Regenerate session ID to prevent fixation attacks + // Regenerate session ID to prevent fixation attacks. + // Explicit save() + log on success/failure so a broken session store + // (missing table, PG read-only, etc.) doesn't silently 200 with no + // persisted session — the symptom that caused the redirect loop. req.session.regenerate((err) => { - if (err) return next(err); + if (err) { + console.error('[auth] session.regenerate failed:', err); + return next(err); + } req.session.userId = user.id; req.session.username = user.username; req.session.role = user.role; - res.json({ - id: user.id, - username: user.username, - display_name: user.display_name, - role: user.role, + req.session.save((saveErr) => { + if (saveErr) { + console.error('[auth] session.save failed:', saveErr); + return next(saveErr); + } + console.log(`[auth] login ok user=${user.username} sid=${req.sessionID?.slice(0,8) || '?'} secure=${req.secure} proto=${req.protocol}`); + res.json({ + id: user.id, + username: user.username, + display_name: user.display_name, + role: user.role, + }); }); }); } catch (err) {