Attic is a cozy space with lofty ambitions. attic.social
at main 136 lines 3.2 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 try { 46 const oAuthClient = createOAuthClient(event); 47 const { url } = await oAuthClient.authorize({ 48 target: { "type": "account", identifier: handle }, 49 }); 50 // Temporary to remember handle across oauth flow 51 event.cookies.set( 52 HANDLE_COOKIE, 53 handle, 54 { 55 httpOnly: true, 56 maxAge: OAUTH_MAX_AGE, 57 path: "/", 58 sameSite: "lax", 59 secure: !dev, 60 }, 61 ); 62 return url; 63 } catch (err) { 64 console.log(err); 65 throw new Error("OAuth failed."); 66 } 67}; 68 69/** 70 * Store the logged in user data 71 */ 72export const updateSession = async ( 73 event: AuthEvent, 74 user: PublicUserData, 75) => { 76 const { cookies, platform } = event; 77 if (platform?.env === undefined) { 78 throw new Error(); 79 } 80 const encrypted = await encryptText( 81 JSON.stringify(user), 82 platform.env.PRIVATE_COOKIE_KEY, 83 ); 84 cookies.set( 85 SESSION_COOKIE, 86 encrypted, 87 { 88 httpOnly: true, 89 maxAge: SESSION_MAX_AGE, 90 path: "/", 91 sameSite: "lax", 92 secure: !dev, 93 }, 94 ); 95}; 96 97/** 98 * Setup OAuth client from cookies 99 */ 100export const restoreSession = async ( 101 event: AuthEvent, 102): Promise<void> => { 103 const { cookies, platform } = event; 104 if (platform?.env === undefined) { 105 throw new Error(); 106 } 107 const encrypted = cookies.get(SESSION_COOKIE); 108 if (encrypted === undefined) { 109 return; 110 } 111 // Parse and validate or delete cookie 112 let data: PublicUserData; 113 try { 114 const decrypted = await decryptText( 115 encrypted, 116 platform?.env.PRIVATE_COOKIE_KEY, 117 ); 118 data = parsePublicUser(JSON.parse(decrypted)); 119 } catch { 120 cookies.delete(SESSION_COOKIE, { path: "/" }); 121 return; 122 } 123 try { 124 const oAuthClient = createOAuthClient(event); 125 const session = await oAuthClient.restore(data.did); 126 const client = new Client({ handler: session }); 127 event.locals.user = { 128 ...data, 129 client, 130 session, 131 }; 132 } catch { 133 cookies.delete(SESSION_COOKIE, { path: "/" }); 134 return; 135 } 136};