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 main 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}