import type { NodeSavedSession, NodeSavedSessionStore, NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node'; import { eq, lt } from 'drizzle-orm'; import type { Database } from '../db'; import { authState, authSession } from '../db/schema'; /** * Database-backed state store for OAuth flow. * States are short-lived (15 min) and used during the authorization flow. */ export class StateStore implements NodeSavedStateStore { constructor(private db: Database) {} async get(key: string): Promise { const result = await this.db.select().from(authState).where(eq(authState.key, key)).limit(1); if (result.length === 0) return undefined; return JSON.parse(result[0].state) as NodeSavedState; } async set(key: string, val: NodeSavedState): Promise { const state = JSON.stringify(val); const now = new Date().toISOString(); await this.db.insert(authState).values({ key, state, createdAt: now }).onConflictDoUpdate({ target: authState.key, set: { state } }); // Clean up old states (older than 15 minutes) const fifteenMinutesAgo = new Date(Date.now() - 15 * 60 * 1000).toISOString(); await this.db.delete(authState).where(lt(authState.createdAt, fifteenMinutesAgo)); } async del(key: string): Promise { await this.db.delete(authState).where(eq(authState.key, key)); } } /** * Database-backed session store for OAuth sessions. * Sessions are long-lived and store the authenticated user's tokens. */ export class SessionStore implements NodeSavedSessionStore { constructor(private db: Database) {} async get(key: string): Promise { const result = await this.db .select() .from(authSession) .where(eq(authSession.key, key)) .limit(1); if (result.length === 0) return undefined; return JSON.parse(result[0].session) as NodeSavedSession; } async set(key: string, val: NodeSavedSession): Promise { const session = JSON.stringify(val); const now = new Date().toISOString(); await this.db .insert(authSession) .values({ key, session, createdAt: now, updatedAt: now }) .onConflictDoUpdate({ target: authSession.key, set: { session, updatedAt: now } }); } async del(key: string): Promise { await this.db.delete(authSession).where(eq(authSession.key, key)); } }