social components inlay.at
atproto components sdui
86
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 160 lines 4.2 kB view raw
1// auth/storage.ts 2import "server-only"; 3import type { 4 NodeSavedSession, 5 NodeSavedSessionStore, 6 NodeSavedState, 7 NodeSavedStateStore, 8 RuntimeLock, 9} from "@atproto/oauth-client-node"; 10import pg from "pg"; 11 12const { Pool } = pg; 13 14let pool: pg.Pool | null = null; 15 16function getPool(): pg.Pool { 17 if (!pool) { 18 const connectionString = process.env.DATABASE_URL; 19 if (!connectionString) { 20 throw new Error("DATABASE_URL is required for auth storage"); 21 } 22 pool = new Pool({ connectionString }); 23 } 24 return pool; 25} 26 27function hashStringToInt(str: string): number { 28 let hash = 0; 29 for (let i = 0; i < str.length; i++) { 30 const char = str.charCodeAt(i); 31 hash = (hash << 5) - hash + char; 32 hash = hash & hash; 33 } 34 return hash; 35} 36 37export const requestLock: RuntimeLock = async (key, fn) => { 38 const db = getPool(); 39 const lockId = hashStringToInt(key); 40 41 const client = await db.connect(); 42 try { 43 await client.query("SELECT pg_advisory_lock($1)", [lockId]); 44 try { 45 return await fn(); 46 } finally { 47 await client.query("SELECT pg_advisory_unlock($1)", [lockId]); 48 } 49 } finally { 50 client.release(); 51 } 52}; 53 54export async function initAuthTables(): Promise<void> { 55 const db = getPool(); 56 57 await db.query(` 58 CREATE TABLE IF NOT EXISTS auth_state ( 59 key TEXT PRIMARY KEY, 60 state TEXT NOT NULL, 61 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 62 ); 63 64 CREATE TABLE IF NOT EXISTS auth_session ( 65 key TEXT PRIMARY KEY, 66 session TEXT NOT NULL, 67 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 68 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 69 ); 70 71 CREATE INDEX IF NOT EXISTS idx_auth_state_created ON auth_state(created_at); 72 `); 73} 74 75export class StateStore implements NodeSavedStateStore { 76 async get(key: string): Promise<NodeSavedState | undefined> { 77 const db = getPool(); 78 const result = await db.query<{ state: string }>( 79 "SELECT state FROM auth_state WHERE key = $1", 80 [key] 81 ); 82 83 if (result.rows.length === 0) return undefined; 84 return JSON.parse(result.rows[0].state) as NodeSavedState; 85 } 86 87 async set(key: string, val: NodeSavedState): Promise<void> { 88 const db = getPool(); 89 const state = JSON.stringify(val); 90 91 await db.query( 92 `INSERT INTO auth_state (key, state) 93 VALUES ($1, $2) 94 ON CONFLICT (key) DO UPDATE SET state = EXCLUDED.state`, 95 [key, state] 96 ); 97 98 // Clean up old states 99 await db.query( 100 "DELETE FROM auth_state WHERE created_at < NOW() - INTERVAL '15 minutes'" 101 ); 102 } 103 104 async del(key: string): Promise<void> { 105 const db = getPool(); 106 await db.query("DELETE FROM auth_state WHERE key = $1", [key]); 107 } 108} 109 110export class SessionStore implements NodeSavedSessionStore { 111 async get(key: string): Promise<NodeSavedSession | undefined> { 112 const db = getPool(); 113 const result = await db.query<{ session: string }>( 114 "SELECT session FROM auth_session WHERE key = $1", 115 [key] 116 ); 117 118 if (result.rows.length === 0) { 119 console.log(`[auth:session] not found: ${key}`); 120 return undefined; 121 } 122 return JSON.parse(result.rows[0].session) as NodeSavedSession; 123 } 124 125 async set(key: string, val: NodeSavedSession): Promise<void> { 126 const db = getPool(); 127 const session = JSON.stringify(val); 128 129 const existing = await db.query( 130 "SELECT 1 FROM auth_session WHERE key = $1", 131 [key] 132 ); 133 const isNew = existing.rows.length === 0; 134 135 await db.query( 136 `INSERT INTO auth_session (key, session, updated_at) 137 VALUES ($1, $2, NOW()) 138 ON CONFLICT (key) DO UPDATE SET 139 session = EXCLUDED.session, 140 updated_at = NOW()`, 141 [key, session] 142 ); 143 144 console.log( 145 `[auth:session] SET ${key} -> ${isNew ? "created" : "updated"}` 146 ); 147 } 148 149 async del(key: string): Promise<void> { 150 const db = getPool(); 151 const result = await db.query( 152 "DELETE FROM auth_session WHERE key = $1 RETURNING key", 153 [key] 154 ); 155 const deleted = result.rowCount && result.rowCount > 0; 156 console.log( 157 `[auth:session] DEL ${key} -> ${deleted ? "deleted" : "not found"}` 158 ); 159 } 160}