packages/server/src/lib/oauth-client.ts 1.8 K raw
1
import { JoseKey } from "@atproto/jwk-jose";
2
import { OAuthClient } from "@atproto/oauth-client";
3
import { AtprotoDohHandleResolver } from "@atproto-labs/handle-resolver";
4
import type { Database } from "bun:sqlite";
5
import { createStateStore, createSessionStore } from "./stores";
6
7
export const OAUTH_SCOPE =
8
	"atproto repo:site.standard.graph.subscription?action=create&action=delete";
9
10
export function createOAuthClient(
11
	db: Database,
12
	clientUrl: string,
13
	clientName = "Sequoia",
14
) {
15
	const clientId = `${clientUrl}/oauth/client-metadata.json`;
16
	const redirectUri = `${clientUrl}/oauth/callback`;
17
18
	const dohEndpoint =
19
		process.env.DOH_ENDPOINT || "https://cloudflare-dns.com/dns-query";
20
21
	return new OAuthClient({
22
		responseMode: "query",
23
		handleResolver: new AtprotoDohHandleResolver({ dohEndpoint }),
24
		clientMetadata: {
25
			client_id: clientId,
26
			client_name: clientName,
27
			client_uri: clientUrl,
28
			redirect_uris: [redirectUri],
29
			grant_types: ["authorization_code", "refresh_token"],
30
			response_types: ["code"],
31
			scope: OAUTH_SCOPE,
32
			token_endpoint_auth_method: "none",
33
			application_type: "web",
34
			dpop_bound_access_tokens: true,
35
		},
36
		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- @atproto Key class mismatch across packages
37
		runtimeImplementation: {
38
			createKey: (algs: string[]) => JoseKey.generate(algs) as any,
39
			getRandomValues: (length: number) =>
40
				crypto.getRandomValues(new Uint8Array(length)),
41
			digest: async (data: Uint8Array, { name }: { name: string }) => {
42
				const buf = await crypto.subtle.digest(
43
					name.replace("sha", "SHA-"),
44
					new Uint8Array(data),
45
				);
46
				return new Uint8Array(buf);
47
			},
48
			requestLock: <T>(_name: string, fn: () => T | PromiseLike<T>) => fn(),
49
		},
50
		stateStore: createStateStore(db),
51
		sessionStore: createSessionStore(db),
52
	});
53
}