import { randomUUID } from "node:crypto"; interface Session { id: string; userId: string; expiresAt: number; } /** * In-memory session store. The API runs as a single container/process, so a * restart clearing every session is an acceptable trade-off for the * simplicity of not needing a persisted session table. */ export class SessionStore { private readonly sessions = new Map(); constructor(private readonly ttlMs: number) {} create(userId: string): Session { const session: Session = { id: randomUUID(), userId, expiresAt: Date.now() + this.ttlMs, }; this.sessions.set(session.id, session); return session; } /** Returns the session and slides its expiry forward, or undefined if gone/expired. */ get(sessionId: string | undefined, slideExpiry = true): Session | undefined { if (!sessionId) return undefined; const session = this.sessions.get(sessionId); if (!session) return undefined; if (session.expiresAt < Date.now()) { this.sessions.delete(sessionId); return undefined; } if (slideExpiry) session.expiresAt = Date.now() + this.ttlMs; return session; } destroy(sessionId: string | undefined): void { if (!sessionId) return; this.sessions.delete(sessionId); } /** Ends every other session for a user, e.g. after a password change. */ destroyAllForUser(userId: string, exceptSessionId?: string): void { for (const [id, session] of this.sessions) { if (session.userId === userId && id !== exceptSessionId) { this.sessions.delete(id); } } } }