fix(auth): ensure sessions table exists + log session.save errors
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.
This commit is contained in:
parent
cfcbec0c85
commit
65684aa577
3 changed files with 38 additions and 8 deletions
|
|
@ -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);
|
||||||
|
|
@ -76,8 +76,11 @@ app.use(
|
||||||
name: 'df.sid',
|
name: 'df.sid',
|
||||||
store: new PgSession({
|
store: new PgSession({
|
||||||
pool,
|
pool,
|
||||||
tableName: 'sessions',
|
tableName: 'sessions',
|
||||||
pruneSessionInterval: 3600,
|
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,
|
secret: SESSION_SECRET,
|
||||||
resave: false,
|
resave: false,
|
||||||
|
|
|
||||||
|
|
@ -127,17 +127,30 @@ router.post('/login', async (req, res, next) => {
|
||||||
// Successful login — clear any accumulated failed attempts
|
// Successful login — clear any accumulated failed attempts
|
||||||
clearAttempts(req, username);
|
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) => {
|
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.userId = user.id;
|
||||||
req.session.username = user.username;
|
req.session.username = user.username;
|
||||||
req.session.role = user.role;
|
req.session.role = user.role;
|
||||||
res.json({
|
req.session.save((saveErr) => {
|
||||||
id: user.id,
|
if (saveErr) {
|
||||||
username: user.username,
|
console.error('[auth] session.save failed:', saveErr);
|
||||||
display_name: user.display_name,
|
return next(saveErr);
|
||||||
role: user.role,
|
}
|
||||||
|
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) {
|
} catch (err) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue