WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
at atb-52-css-token-extraction 105 lines 3.0 kB view raw
1/** 2 * Store adapters for @atproto/oauth-client-node 3 * 4 * The OAuth library expects stores that match the SimpleStore<K, V> interface. 5 * This file provides adapters that bridge our internal storage to the library's format. 6 * Each adapter wraps a generic TTLStore with the async interface expected by the library. 7 */ 8 9import type { NodeSavedState, NodeSavedSession } from "@atproto/oauth-client-node"; 10import { TTLStore } from "./ttl-store.js"; 11 12/** 13 * Internal state wrapper with timestamp for expiration tracking. 14 */ 15interface StateEntry { 16 state: NodeSavedState; 17 createdAt: number; 18} 19 20/** 10-minute TTL for OAuth authorization state. */ 21const STATE_TTL_MS = 10 * 60 * 1000; 22 23/** 24 * State store adapter for OAuth authorization flow. 25 * Bridges our in-memory storage to the library's expected async SimpleStore interface. 26 */ 27export class OAuthStateStore { 28 private store: TTLStore<StateEntry>; 29 30 constructor() { 31 this.store = new TTLStore<StateEntry>( 32 (entry) => Date.now() - entry.createdAt > STATE_TTL_MS, 33 "oauth_state_store" 34 ); 35 } 36 37 async set(key: string, internalState: NodeSavedState): Promise<void> { 38 this.store.set(key, { 39 state: internalState, 40 createdAt: Date.now(), 41 }); 42 } 43 44 async get(key: string): Promise<NodeSavedState | undefined> { 45 const entry = this.store.get(key); 46 return entry?.state; 47 } 48 49 async del(key: string): Promise<void> { 50 this.store.delete(key); 51 } 52 53 /** 54 * Stop cleanup timer (for graceful shutdown). 55 */ 56 destroy(): void { 57 this.store.destroy(); 58 } 59} 60 61/** 62 * Session store adapter for OAuth sessions. 63 * Bridges our in-memory storage to the library's expected async SimpleStore interface. 64 * 65 * The library stores sessions indexed by DID (sub), and handles token refresh internally. 66 */ 67export class OAuthSessionStore { 68 private store: TTLStore<NodeSavedSession>; 69 70 constructor() { 71 this.store = new TTLStore<NodeSavedSession>( 72 (session) => { 73 // Only expire sessions where access token is expired and there's no refresh token. 74 // Keep sessions with refresh tokens — the library will handle refresh. 75 if (!session.tokenSet.refresh_token && session.tokenSet.expires_at) { 76 const expiresAt = new Date(session.tokenSet.expires_at).getTime(); 77 return expiresAt < Date.now(); 78 } 79 return false; 80 }, 81 "oauth_session_store" 82 ); 83 } 84 85 async set(sub: string, session: NodeSavedSession): Promise<void> { 86 this.store.set(sub, session); 87 } 88 89 async get(sub: string): Promise<NodeSavedSession | undefined> { 90 // Use getUnchecked so the library can manage token refresh internally. 91 // Background cleanup still evicts truly expired sessions without refresh tokens. 92 return this.store.getUnchecked(sub); 93 } 94 95 async del(sub: string): Promise<void> { 96 this.store.delete(sub); 97 } 98 99 /** 100 * Stop cleanup timer (for graceful shutdown). 101 */ 102 destroy(): void { 103 this.store.destroy(); 104 } 105}