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',
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue