Meal planning and recipes sorted
at main 115 lines 3.4 kB view raw
1import { 2 JoseKey, 3 Keyset, 4 NodeOAuthClient, 5 buildAtprotoLoopbackClientMetadata, 6} from "@atproto/oauth-client-node"; 7import type { 8 NodeSavedSession, 9 NodeSavedState, 10 OAuthClientMetadataInput, 11} from "@atproto/oauth-client-node"; 12import { getDb } from "../db"; 13 14export const SCOPE = "atproto account:email repo:com.example.record"; 15 16let client: NodeOAuthClient | null = null; 17 18const PUBLIC_URL = process.env.PUBLIC_URL; 19const PRIVATE_KEY = process.env.PRIVATE_KEY; 20 21function getClientMetadata(): OAuthClientMetadataInput { 22 if (PUBLIC_URL) { 23 return { 24 client_id: `${PUBLIC_URL}/oauth-client-metadata.json`, 25 client_name: "OAuth Tutorial", 26 client_uri: PUBLIC_URL, 27 redirect_uris: [`${PUBLIC_URL}/oauth/callback`], 28 grant_types: ["authorization_code", "refresh_token"], 29 response_types: ["code"], 30 scope: SCOPE, 31 token_endpoint_auth_method: "private_key_jwt" as const, 32 token_endpoint_auth_signing_alg: "ES256" as const, // must match the alg in scripts/gen-key.ts 33 jwks_uri: `${PUBLIC_URL}/.well-known/jwks.json`, 34 dpop_bound_access_tokens: true, 35 }; 36 } else { 37 return buildAtprotoLoopbackClientMetadata({ 38 scope: SCOPE, 39 redirect_uris: ["http://127.0.0.1:3000/oauth/callback"], 40 }); 41 } 42} 43 44async function getKeyset(): Promise<Keyset | undefined> { 45 if (PUBLIC_URL && PRIVATE_KEY) { 46 return new Keyset([await JoseKey.fromJWK(JSON.parse(PRIVATE_KEY))]); 47 } else { 48 return undefined; 49 } 50} 51 52export async function getOAuthClient(): Promise<NodeOAuthClient> { 53 if (client) return client; 54 55 client = new NodeOAuthClient({ 56 clientMetadata: getClientMetadata(), 57 keyset: await getKeyset(), 58 59 stateStore: { 60 async get(key: string) { 61 const db = getDb(); 62 const row = await db 63 .selectFrom("auth_state") 64 .select("value") 65 .where("key", "=", key) 66 .executeTakeFirst(); 67 return row ? JSON.parse(row.value) : undefined; 68 }, 69 async set(key: string, value: NodeSavedState) { 70 const db = getDb(); 71 const valueJson = JSON.stringify(value); 72 await db 73 .insertInto("auth_state") 74 .values({ key, value: valueJson }) 75 .onConflict((oc) => 76 oc.column("key").doUpdateSet({ value: valueJson }), 77 ) 78 .execute(); 79 }, 80 async del(key: string) { 81 const db = getDb(); 82 await db.deleteFrom("auth_state").where("key", "=", key).execute(); 83 }, 84 }, 85 86 sessionStore: { 87 async get(key: string) { 88 const db = getDb(); 89 const row = await db 90 .selectFrom("auth_session") 91 .select("value") 92 .where("key", "=", key) 93 .executeTakeFirst(); 94 return row ? JSON.parse(row.value) : undefined; 95 }, 96 async set(key: string, value: NodeSavedSession) { 97 const db = getDb(); 98 const valueJson = JSON.stringify(value); 99 await db 100 .insertInto("auth_session") 101 .values({ key, value: valueJson }) 102 .onConflict((oc) => 103 oc.column("key").doUpdateSet({ value: valueJson }), 104 ) 105 .execute(); 106 }, 107 async del(key: string) { 108 const db = getDb(); 109 await db.deleteFrom("auth_session").where("key", "=", key).execute(); 110 }, 111 }, 112 }); 113 114 return client; 115}