import { randomBytes } from "node:crypto"; import { getDb } from "../index.ts"; /** * Mint an opaque session id for a DID. The id is unguessable (256 bits of * randomness), so possessing it — not merely knowing a public DID — is what * authenticates the holder. `expiresAt` is an ISO 8601 timestamp. */ export function createAppSession(did: string, expiresAt: string): string { const id = randomBytes(32).toString("base64url"); // Normalize the ISO timestamp to SQLite's `YYYY-MM-DD HH:MM:SS` via // datetime(): a raw ISO string ("...T...Z") sorts lexicographically *after* // datetime('now') (space separator), which would make every row look unexpired. getDb().run( "INSERT INTO app_sessions (id, did, expires_at) VALUES (?, ?, datetime(?))", [id, did, expiresAt], ); return id; } /** Returns the DID only if the session exists and has not expired. */ export function getAppSessionDid(id: string): string | null { const row = getDb() .query( "SELECT did FROM app_sessions WHERE id = ? AND expires_at > datetime('now')", ) .get(id) as { did: string } | null; return row?.did ?? null; } export function deleteAppSession(id: string): void { getDb().run("DELETE FROM app_sessions WHERE id = ?", [id]); } /** Housekeeping: drop expired rows so the table stays bounded. */ export function pruneExpiredAppSessions(): void { getDb().run("DELETE FROM app_sessions WHERE expires_at <= datetime('now')"); }