Attic is a cozy space with lofty ambitions. attic.social
at 2ca2aaa2fbcd0dc231f1821c8f5b545532fa685e 131 lines 3.0 kB view raw
1import { dev } from "$app/environment"; 2import { 3 HANDLE_COOKIE, 4 OAUTH_MAX_AGE, 5 SESSION_COOKIE, 6 SESSION_MAX_AGE, 7} from "$lib/server/constants"; 8import { decryptText, encryptText } from "$lib/server/crypto"; 9import { createOAuthClient } from "$lib/server/oauth"; 10import type { AuthEvent } from "$lib/types"; 11import { parsePublicUser, type PublicUserData } from "$lib/valibot"; 12import { Client } from "@atcute/client"; 13import { isHandle } from "@atcute/lexicons/syntax"; 14 15/** 16 * Logout 17 */ 18export const destroySession = async ( 19 event: AuthEvent, 20): Promise<void> => { 21 event.cookies.delete(SESSION_COOKIE, { path: "/" }); 22 if (event.locals.user) { 23 try { 24 const oAuthClient = createOAuthClient(event); 25 await oAuthClient.revoke(event.locals.user.did); 26 } catch { 27 // Do nothing? 28 } 29 event.locals.user = undefined; 30 } 31 event.locals.oAuthClient = undefined; 32}; 33 34/** 35 * Begin auth flow 36 * @returns {URL} OAuth redirect 37 */ 38export const startSession = async ( 39 event: AuthEvent, 40 handle: string, 41): Promise<URL> => { 42 if (isHandle(handle) === false) { 43 throw new Error("invalid handle"); 44 } 45 const oAuthClient = createOAuthClient(event); 46 const { url } = await oAuthClient.authorize({ 47 target: { "type": "account", identifier: handle }, 48 }); 49 // Temporary to remember handle across oauth flow 50 event.cookies.set( 51 HANDLE_COOKIE, 52 handle, 53 { 54 httpOnly: true, 55 maxAge: OAUTH_MAX_AGE, 56 path: "/", 57 sameSite: "lax", 58 secure: !dev, 59 }, 60 ); 61 return url; 62}; 63 64/** 65 * Store the logged in user data 66 */ 67export const updateSession = async ( 68 event: AuthEvent, 69 user: PublicUserData, 70) => { 71 const { cookies, platform } = event; 72 if (platform?.env === undefined) { 73 throw new Error(); 74 } 75 const encrypted = await encryptText( 76 JSON.stringify(user), 77 platform.env.PRIVATE_COOKIE_KEY, 78 ); 79 cookies.set( 80 SESSION_COOKIE, 81 encrypted, 82 { 83 httpOnly: true, 84 maxAge: SESSION_MAX_AGE, 85 path: "/", 86 sameSite: "lax", 87 secure: !dev, 88 }, 89 ); 90}; 91 92/** 93 * Setup OAuth client from cookies 94 */ 95export const restoreSession = async ( 96 event: AuthEvent, 97): Promise<void> => { 98 const { cookies, platform } = event; 99 if (platform?.env === undefined) { 100 throw new Error(); 101 } 102 const encrypted = cookies.get(SESSION_COOKIE); 103 if (encrypted === undefined) { 104 return; 105 } 106 // Parse and validate or delete cookie 107 let data: PublicUserData; 108 try { 109 const decrypted = await decryptText( 110 encrypted, 111 platform?.env.PRIVATE_COOKIE_KEY, 112 ); 113 data = parsePublicUser(JSON.parse(decrypted)); 114 } catch { 115 cookies.delete(SESSION_COOKIE, { path: "/" }); 116 return; 117 } 118 try { 119 const oAuthClient = createOAuthClient(event); 120 const session = await oAuthClient.restore(data.did); 121 const client = new Client({ handler: session }); 122 event.locals.user = { 123 ...data, 124 client, 125 session, 126 }; 127 } catch { 128 cookies.delete(SESSION_COOKIE, { path: "/" }); 129 return; 130 } 131};