/** * Store adapters for @atproto/oauth-client-node * * The OAuth library expects stores that match the SimpleStore interface. * This file provides adapters that bridge our internal storage to the library's format. * Each adapter wraps a generic TTLStore with the async interface expected by the library. */ import type { NodeSavedState, NodeSavedSession } from "@atproto/oauth-client-node"; import { TTLStore } from "./ttl-store.js"; /** * Internal state wrapper with timestamp for expiration tracking. */ interface StateEntry { state: NodeSavedState; createdAt: number; } /** 10-minute TTL for OAuth authorization state. */ const STATE_TTL_MS = 10 * 60 * 1000; /** * State store adapter for OAuth authorization flow. * Bridges our in-memory storage to the library's expected async SimpleStore interface. */ export class OAuthStateStore { private store: TTLStore; constructor() { this.store = new TTLStore( (entry) => Date.now() - entry.createdAt > STATE_TTL_MS, "oauth_state_store" ); } async set(key: string, internalState: NodeSavedState): Promise { this.store.set(key, { state: internalState, createdAt: Date.now(), }); } async get(key: string): Promise { const entry = this.store.get(key); return entry?.state; } async del(key: string): Promise { this.store.delete(key); } /** * Stop cleanup timer (for graceful shutdown). */ destroy(): void { this.store.destroy(); } } /** * Session store adapter for OAuth sessions. * Bridges our in-memory storage to the library's expected async SimpleStore interface. * * The library stores sessions indexed by DID (sub), and handles token refresh internally. */ export class OAuthSessionStore { private store: TTLStore; constructor() { this.store = new TTLStore( (session) => { // Only expire sessions where access token is expired and there's no refresh token. // Keep sessions with refresh tokens — the library will handle refresh. if (!session.tokenSet.refresh_token && session.tokenSet.expires_at) { const expiresAt = new Date(session.tokenSet.expires_at).getTime(); return expiresAt < Date.now(); } return false; }, "oauth_session_store" ); } async set(sub: string, session: NodeSavedSession): Promise { this.store.set(sub, session); } async get(sub: string): Promise { // Use getUnchecked so the library can manage token refresh internally. // Background cleanup still evicts truly expired sessions without refresh tokens. return this.store.getUnchecked(sub); } async del(sub: string): Promise { this.store.delete(sub); } /** * Stop cleanup timer (for graceful shutdown). */ destroy(): void { this.store.destroy(); } }