Barazo AppView backend barazo.forum
at main 115 lines 3.7 kB view raw
1import type { NodeSavedSession, NodeSavedState } from '@atproto/oauth-client-node' 2import type { Cache } from '../cache/index.js' 3import type { Logger } from '../lib/logger.js' 4 5const STATE_KEY_PREFIX = 'barazo:oauth:state:' 6const SESSION_KEY_PREFIX = 'barazo:oauth:session:' 7 8/** Default state TTL: 5 minutes (OAuth state is short-lived) */ 9const DEFAULT_STATE_TTL = 300 10 11/** 12 * Valkey-backed store for OAuth authorization state. 13 * State entries are short-lived (5 minutes) and used during the 14 * authorization code flow between redirect and callback. 15 */ 16export class ValkeyStateStore { 17 private readonly cache: Cache 18 private readonly logger: Logger 19 20 constructor(cache: Cache, logger: Logger) { 21 this.cache = cache 22 this.logger = logger 23 } 24 25 async set(key: string, state: NodeSavedState): Promise<void> { 26 const cacheKey = `${STATE_KEY_PREFIX}${key}` 27 try { 28 await this.cache.set(cacheKey, JSON.stringify(state), 'EX', DEFAULT_STATE_TTL) 29 this.logger.debug({ key: cacheKey }, 'OAuth state stored') 30 } catch (err: unknown) { 31 this.logger.error({ err, key: cacheKey }, 'Failed to store OAuth state') 32 throw err 33 } 34 } 35 36 async get(key: string): Promise<NodeSavedState | undefined> { 37 const cacheKey = `${STATE_KEY_PREFIX}${key}` 38 try { 39 const data = await this.cache.get(cacheKey) 40 if (data === null) { 41 this.logger.debug({ key: cacheKey }, 'OAuth state not found') 42 return undefined 43 } 44 return JSON.parse(data) as NodeSavedState 45 } catch (err: unknown) { 46 this.logger.error({ err, key: cacheKey }, 'Failed to retrieve OAuth state') 47 throw err 48 } 49 } 50 51 async del(key: string): Promise<void> { 52 const cacheKey = `${STATE_KEY_PREFIX}${key}` 53 try { 54 await this.cache.del(cacheKey) 55 this.logger.debug({ key: cacheKey }, 'OAuth state deleted') 56 } catch (err: unknown) { 57 this.logger.error({ err, key: cacheKey }, 'Failed to delete OAuth state') 58 throw err 59 } 60 } 61} 62 63/** 64 * Valkey-backed store for OAuth sessions. 65 * Sessions persist across requests and are keyed by the user's DID (sub). 66 * Default TTL is 7 days (604800 seconds), configurable via OAUTH_SESSION_TTL. 67 */ 68export class ValkeySessionStore { 69 private readonly cache: Cache 70 private readonly logger: Logger 71 private readonly ttl: number 72 73 constructor(cache: Cache, logger: Logger, ttl: number) { 74 this.cache = cache 75 this.logger = logger 76 this.ttl = ttl 77 } 78 79 async set(sub: string, session: NodeSavedSession): Promise<void> { 80 const cacheKey = `${SESSION_KEY_PREFIX}${sub}` 81 try { 82 await this.cache.set(cacheKey, JSON.stringify(session), 'EX', this.ttl) 83 this.logger.debug({ key: cacheKey }, 'OAuth session stored') 84 } catch (err: unknown) { 85 this.logger.error({ err, key: cacheKey }, 'Failed to store OAuth session') 86 throw err 87 } 88 } 89 90 async get(sub: string): Promise<NodeSavedSession | undefined> { 91 const cacheKey = `${SESSION_KEY_PREFIX}${sub}` 92 try { 93 const data = await this.cache.get(cacheKey) 94 if (data === null) { 95 this.logger.debug({ key: cacheKey }, 'OAuth session not found') 96 return undefined 97 } 98 return JSON.parse(data) as NodeSavedSession 99 } catch (err: unknown) { 100 this.logger.error({ err, key: cacheKey }, 'Failed to retrieve OAuth session') 101 throw err 102 } 103 } 104 105 async del(sub: string): Promise<void> { 106 const cacheKey = `${SESSION_KEY_PREFIX}${sub}` 107 try { 108 await this.cache.del(cacheKey) 109 this.logger.debug({ key: cacheKey }, 'OAuth session deleted') 110 } catch (err: unknown) { 111 this.logger.error({ err, key: cacheKey }, 'Failed to delete OAuth session') 112 throw err 113 } 114 } 115}