forked from
baileytownsend.dev/atproto-sveltekit-template
WIP: Another at:// production from me
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};