fix: recover when sessions table is missing

This commit is contained in:
Nico 2026-02-18 14:52:35 -08:00
parent c3c0c33339
commit c1259f0bf5

View File

@ -2,27 +2,10 @@ const crypto = require("crypto");
const pool = require("../db/pool"); const pool = require("../db/pool");
const { SESSION_TTL_DAYS } = require("../utils/session-cookie"); const { SESSION_TTL_DAYS } = require("../utils/session-cookie");
function generateSessionId() { const INSERT_SESSION_SQL = `INSERT INTO sessions (id, user_id, expires_at, user_agent)
if (typeof crypto.randomUUID === "function") {
return crypto.randomUUID().replace(/-/g, "") + crypto.randomBytes(8).toString("hex");
}
return crypto.randomBytes(32).toString("hex");
}
exports.createSession = async (userId, userAgent = null) => {
const id = generateSessionId();
const result = await pool.query(
`INSERT INTO sessions (id, user_id, expires_at, user_agent)
VALUES ($1, $2, NOW() + ($3 || ' days')::interval, $4) VALUES ($1, $2, NOW() + ($3 || ' days')::interval, $4)
RETURNING id, user_id, created_at, expires_at`, RETURNING id, user_id, created_at, expires_at`;
[id, userId, String(SESSION_TTL_DAYS), userAgent] const SELECT_ACTIVE_SESSION_SQL = `SELECT
);
return result.rows[0];
};
exports.getActiveSessionWithUser = async (sessionId) => {
const result = await pool.query(
`SELECT
s.id, s.id,
s.user_id, s.user_id,
s.expires_at, s.expires_at,
@ -31,26 +14,110 @@ exports.getActiveSessionWithUser = async (sessionId) => {
FROM sessions s FROM sessions s
JOIN users u ON u.id = s.user_id JOIN users u ON u.id = s.user_id
WHERE s.id = $1 WHERE s.id = $1
AND s.expires_at > NOW()`, AND s.expires_at > NOW()`;
[sessionId]
let ensureSessionsTablePromise = null;
function generateSessionId() {
if (typeof crypto.randomUUID === "function") {
return crypto.randomUUID().replace(/-/g, "") + crypto.randomBytes(8).toString("hex");
}
return crypto.randomBytes(32).toString("hex");
}
function isUndefinedTableError(error) {
return error && error.code === "42P01";
}
async function ensureSessionsTable() {
if (!ensureSessionsTablePromise) {
ensureSessionsTablePromise = (async () => {
await pool.query(`CREATE TABLE IF NOT EXISTS sessions (
id VARCHAR(128) PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
user_agent TEXT
);`);
await pool.query(
"CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);"
); );
await pool.query(
"CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);"
);
})().catch((error) => {
ensureSessionsTablePromise = null;
throw error;
});
}
await ensureSessionsTablePromise;
}
async function insertSession(id, userId, userAgent) {
const result = await pool.query(INSERT_SESSION_SQL, [
id,
userId,
String(SESSION_TTL_DAYS),
userAgent,
]);
return result.rows[0];
}
exports.createSession = async (userId, userAgent = null) => {
const id = generateSessionId();
try {
return await insertSession(id, userId, userAgent);
} catch (error) {
if (!isUndefinedTableError(error)) {
throw error;
}
await ensureSessionsTable();
return insertSession(id, userId, userAgent);
}
};
exports.getActiveSessionWithUser = async (sessionId) => {
let result;
try {
result = await pool.query(SELECT_ACTIVE_SESSION_SQL, [sessionId]);
} catch (error) {
if (isUndefinedTableError(error)) {
return null;
}
throw error;
}
const session = result.rows[0] || null; const session = result.rows[0] || null;
if (!session) return null; if (!session) return null;
try {
await pool.query( await pool.query(
`UPDATE sessions `UPDATE sessions
SET last_seen_at = NOW() SET last_seen_at = NOW()
WHERE id = $1`, WHERE id = $1`,
[sessionId] [sessionId]
); );
} catch (error) {
if (!isUndefinedTableError(error)) {
throw error;
}
}
return session; return session;
}; };
exports.deleteSession = async (sessionId) => { exports.deleteSession = async (sessionId) => {
try {
await pool.query( await pool.query(
`DELETE FROM sessions WHERE id = $1`, `DELETE FROM sessions WHERE id = $1`,
[sessionId] [sessionId]
); );
} catch (error) {
if (!isUndefinedTableError(error)) {
throw error;
}
}
}; };