this repo has no description
1import {
2 buildAtprotoLoopbackClientMetadata,
3 JoseKey,
4 Keyset,
5 NodeOAuthClient,
6 NodeSavedSession,
7 NodeSavedState
8} from "@atproto/oauth-client-node";
9import { getDB } from "../db";
10
11declare global {
12 var _oauthClient: NodeOAuthClient | undefined;
13}
14
15const PRIVATE_KEY = process.env.PRIVATE_KEY;
16const PUBLIC_URL = process.env.PUBLIC_URL;
17
18export const SCOPE =
19 "atproto rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview";
20
21const getClientMetadata = () => {
22 if (PUBLIC_URL) {
23 return {
24 client_id: `${PUBLIC_URL}/oauth-client-metadata.json`,
25 client_name: "bambü",
26 client_uri: PUBLIC_URL,
27 dpop_bound_access_tokens: true,
28 grant_types: ["authorization_code", "refresh_token"],
29 jwks_uri: `${PUBLIC_URL}/.well-known/jwks.json`,
30 redirect_uris: [`${PUBLIC_URL}/oauth/callback`],
31 response_types: ["code"],
32 scope: SCOPE,
33 token_endpoint_auth_method: "private_key_jwt" as const,
34 token_endpoint_auth_signing_alg: "ES256" as const
35 };
36 }
37
38 return buildAtprotoLoopbackClientMetadata({
39 redirect_uris: ["http://127.0.0.1:3000/oauth/callback"],
40 scope: SCOPE
41 });
42};
43
44const getKeyset = async () => {
45 if (PUBLIC_URL && PRIVATE_KEY) {
46 return new Keyset([await JoseKey.fromJWK(JSON.parse(PRIVATE_KEY))]);
47 }
48
49 return undefined;
50};
51
52export const getOAuthClient = async () => {
53 if (globalThis._oauthClient) {
54 return globalThis._oauthClient;
55 }
56
57 globalThis._oauthClient = new NodeOAuthClient({
58 clientMetadata: getClientMetadata(),
59 keyset: await getKeyset(),
60 sessionStore: {
61 del: async (key: string) => {
62 const db = getDB();
63
64 await db
65 ?.deleteFrom("auth_session")
66 .where("key", "=", key)
67 .execute();
68 },
69 get: async (key: string) => {
70 const db = getDB();
71 const row = await db
72 ?.selectFrom("auth_session")
73 .select("value")
74 .where("key", "=", key)
75 .executeTakeFirst();
76
77 return row ? JSON.parse(row.value) : undefined;
78 },
79 set: async (key: string, value: NodeSavedSession) => {
80 const db = getDB();
81 const valueJSON = JSON.stringify(value);
82
83 await db
84 ?.insertInto("auth_session")
85 .values({ key, value: valueJSON })
86 .onConflict((oc) =>
87 oc.column("key").doUpdateSet({ value: valueJSON })
88 )
89 .execute();
90 }
91 },
92 stateStore: {
93 del: async (key: string) => {
94 const db = getDB();
95
96 await db
97 ?.deleteFrom("auth_state")
98 .where("key", "=", key)
99 .execute();
100 },
101 get: async (key: string) => {
102 const db = getDB();
103 const row = await db
104 ?.selectFrom("auth_state")
105 .select("value")
106 .where("key", "=", key)
107 .executeTakeFirst();
108
109 return row ? JSON.parse(row.value) : undefined;
110 },
111 set: async (key: string, value: NodeSavedState) => {
112 const db = getDB();
113 const valueJSON = JSON.stringify(value);
114
115 await db
116 ?.insertInto("auth_state")
117 .values({ key, value: valueJSON })
118 .onConflict((oc) =>
119 oc.column("key").doUpdateSet({ value: valueJSON })
120 )
121 .execute();
122 }
123 }
124 });
125
126 return globalThis._oauthClient;
127};