62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import { randomBytes } from "node:crypto";
|
|
|
|
// Server-side session store. The browser never sees any of this; it only holds
|
|
// the opaque id (see cookie.ts). Keyed by a random 256-bit session id.
|
|
//
|
|
// NOTE: this is in-memory and per-process. It is fine for now but does not
|
|
// survive a restart and is not shared across instances. A durable store
|
|
// (e.g. Redis) is a later concern, tracked outside this task.
|
|
|
|
export interface SessionPerson {
|
|
userId: string;
|
|
name: string;
|
|
email: string;
|
|
pictureUrl: string | null;
|
|
/** The person's id on the feed (feed GET /api/v1/auth/me data.id); null when unknown. */
|
|
feedUserId?: string | null;
|
|
}
|
|
|
|
export interface SessionData {
|
|
profileAccessToken: string;
|
|
/** Epoch milliseconds when the profile access token expires, or null if unknown. */
|
|
profileAccessTokenExpiresAt: number | null;
|
|
profileRefreshToken: string;
|
|
feedAccessToken: string;
|
|
/** Epoch milliseconds when the feed access token expires, or null if unknown. */
|
|
feedAccessTokenExpiresAt: number | null;
|
|
person: SessionPerson;
|
|
createdAt: number;
|
|
}
|
|
|
|
// Survive Next.js dev HMR by stashing the map on globalThis.
|
|
const globalRef = globalThis as typeof globalThis & {
|
|
__mfbSessionStore?: Map<string, SessionData>;
|
|
};
|
|
|
|
const store: Map<string, SessionData> =
|
|
globalRef.__mfbSessionStore ?? new Map<string, SessionData>();
|
|
globalRef.__mfbSessionStore = store;
|
|
|
|
export function createSession(data: SessionData): string {
|
|
const id = randomBytes(32).toString("hex");
|
|
store.set(id, data);
|
|
return id;
|
|
}
|
|
|
|
export function getSession(id: string): SessionData | null {
|
|
return store.get(id) ?? null;
|
|
}
|
|
|
|
/**
|
|
* Replace the data for an existing session. Used after a token refresh to
|
|
* persist the new tokens. A no-op when the session is already gone, so a
|
|
* refresh cannot resurrect a signed-out session.
|
|
*/
|
|
export function updateSession(id: string, data: SessionData): void {
|
|
if (store.has(id)) store.set(id, data);
|
|
}
|
|
|
|
export function deleteSession(id: string): void {
|
|
store.delete(id);
|
|
}
|