feat: passport-local authentication, story ownership
All checks were successful
/ checks (push) Successful in 38s
/ deploy (push) Successful in 11s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nholtware 2026-11-05 13:33:51 -06:00
parent 0ae3f6109b
commit afbc5e5254
16 changed files with 995 additions and 73 deletions

70
src/lib/session-store.ts Normal file
View file

@ -0,0 +1,70 @@
import session from "express-session";
import type { Db } from "./db.js";
/** Postgres-backed sessions so every pod sees the same login. */
export class PgSessionStore extends session.Store {
private readonly timer: NodeJS.Timeout;
constructor(
private readonly db: Db,
private readonly ttlMs: number,
) {
super();
// Expired rows are ignored on read; sweep them occasionally.
this.timer = setInterval(() => {
void this.db
.query("DELETE FROM sessions WHERE expire < now()")
.catch(() => {});
}, 10 * 60_000);
this.timer.unref();
}
private expiry(sess: session.SessionData): Date {
const exp = sess.cookie?.expires;
return exp ? new Date(exp) : new Date(Date.now() + this.ttlMs);
}
get(
sid: string,
cb: (err?: unknown, sess?: session.SessionData | null) => void,
) {
this.db
.query<{ sess: session.SessionData }>(
"SELECT sess FROM sessions WHERE sid = $1 AND expire > now()",
[sid],
)
.then((r) => cb(null, r.rows[0]?.sess ?? null), cb);
}
set(sid: string, sess: session.SessionData, cb?: (err?: unknown) => void) {
this.db
.query(
`INSERT INTO sessions (sid, sess, expire) VALUES ($1, $2, $3)
ON CONFLICT (sid) DO UPDATE SET sess = EXCLUDED.sess, expire = EXCLUDED.expire`,
[sid, JSON.stringify(sess), this.expiry(sess)],
)
.then(
() => cb?.(),
(e) => cb?.(e),
);
}
touch(sid: string, sess: session.SessionData, cb?: (err?: unknown) => void) {
this.db
.query("UPDATE sessions SET expire = $2 WHERE sid = $1", [
sid,
this.expiry(sess),
])
.then(
() => cb?.(),
(e) => cb?.(e),
);
}
destroy(sid: string, cb?: (err?: unknown) => void) {
this.db.query("DELETE FROM sessions WHERE sid = $1", [sid]).then(
() => cb?.(),
(e) => cb?.(e),
);
}
}