A build your own ATProto adventure, OAuth already figured out for you.
at main 3.2 kB view raw
1import { db } from '$lib/server/db'; 2import type { Handle, ServerInit } from '@sveltejs/kit'; 3import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; 4import { env } from '$env/dynamic/private'; 5import { keyValueStore } from '$lib/server/db/schema'; 6import { and, eq, lt } from 'drizzle-orm'; 7import { STATE_STORE } from '$lib/server/cache'; 8import { logger } from '$lib/server/logger'; 9import { HOUR } from '@atproto/common'; 10import { getSessionManager, SessionRestorationError } from '$lib/server/session'; 11 12const clearExpiredStates = async () => { 13 try { 14 logger.info('Running cleanup of the state store'); 15 const oneHourAgo = new Date(Date.now() - HOUR); 16 const result = await db 17 .delete(keyValueStore) 18 .where( 19 and( 20 eq(keyValueStore.storeName, STATE_STORE), 21 lt(keyValueStore.createdAt, oneHourAgo)) 22 ); 23 24 if (result.changes > 0) { 25 logger.info(`Cleaned up ${result.changes} expired key(s) from keyValueStore`); 26 } 27 } catch (err) { 28 logger.error(`${(err as Error).message}`); 29 } 30}; 31 32 33export const init: ServerInit = async () => { 34 // Run Drizzle migrations on server startup 35 migrate(db, { migrationsFolder: env.MIGRATIONS_FOLDER ?? 'drizzle' }); 36 37 await clearExpiredStates(); 38 39 // Start a background job to clean up state every hour, which is recommended in the oauth docs 40 setInterval(async () => { 41 await clearExpiredStates(); 42 //TODO prob should do one for the session store as well for expired sessions 43 }, HOUR); // Run every hour 44}; 45 46export const handle: Handle = async ({ event, resolve }) => { 47 const token = event.cookies.get('session') ?? null; 48 if (token === null) { 49 event.locals.session = null; 50 event.locals.atpAgent = null; 51 return resolve(event); 52 } 53 54 const sessionManager = await getSessionManager(); 55 56 try { 57 const { atpAgent, did, handle } = await sessionManager.getSessionFromRequest(event); 58 59 if(atpAgent == null){ 60 event.locals.session = null; 61 event.locals.atpAgent = null; 62 return resolve(event); 63 } 64 65 // Store atpAgent in locals (server-side only, not serialized) 66 event.locals.atpAgent = atpAgent; 67 68 // Store only serializable data in session (gets passed to client via load functions) 69 event.locals.session = { 70 did, 71 handle 72 }; 73 } catch (err) { 74 if (err instanceof SessionRestorationError) { 75 //You can propagate this error to the frontend to let your users know their session unexpectedly ended 76 //I opted out of not completely implementing this since everyone may have a different idea of what to do in their apps 77 //For instance I would use the cache to create a flash message that when loaded it deletes and show it on the layout 78 } else { 79 // Unexpected error, re-throw 80 throw err; 81 } 82 83 event.locals.session = null; 84 event.locals.atpAgent = null; 85 } 86 87 return resolve(event); 88};