Scratch space for learning atproto app development
1import {
2 Keyset,
3 JoseKey,
4 atprotoLoopbackClientMetadata,
5 NodeOAuthClient,
6 OAuthClientMetadataInput,
7} from '@atproto/oauth-client-node'
8import assert from 'node:assert'
9
10import type { Database } from '#/db'
11import { env } from '#/env'
12import { SessionStore, StateStore } from './storage'
13
14export async function createOAuthClient(db: Database) {
15 // Confidential client require a keyset accessible on the internet. Non
16 // internet clients (e.g. development) cannot expose a keyset on the internet
17 // so they can't be private..
18 const keyset =
19 env.PUBLIC_URL && env.PRIVATE_KEYS
20 ? new Keyset(
21 await Promise.all(
22 env.PRIVATE_KEYS.map((jwk) => JoseKey.fromJWK(jwk)),
23 ),
24 )
25 : undefined
26
27 assert(
28 !env.PUBLIC_URL || keyset?.size,
29 'ATProto requires backend clients to be confidential. Make sure to set the PRIVATE_KEYS environment variable.',
30 )
31
32 // If a keyset is defined (meaning the client is confidential). Let's make
33 // sure it has a private key for signing. Note: findPrivateKey will throw if
34 // the keyset does not contain a suitable private key.
35 const pk = keyset?.findPrivateKey({ use: 'sig' })
36
37 const clientMetadata: OAuthClientMetadataInput = env.PUBLIC_URL
38 ? {
39 client_name: 'Statusphere Example App',
40 client_id: `${env.PUBLIC_URL}/oauth-client-metadata.json`,
41 jwks_uri: `${env.PUBLIC_URL}/.well-known/jwks.json`,
42 redirect_uris: [`${env.PUBLIC_URL}/oauth/callback`],
43 scope: 'atproto transition:generic',
44 grant_types: ['authorization_code', 'refresh_token'],
45 response_types: ['code'],
46 application_type: 'web',
47 token_endpoint_auth_method: pk ? 'private_key_jwt' : 'none',
48 token_endpoint_auth_signing_alg: pk ? pk.alg : undefined,
49 dpop_bound_access_tokens: true,
50 }
51 : atprotoLoopbackClientMetadata(
52 `http://localhost?${new URLSearchParams([
53 ['redirect_uri', `http://127.0.0.1:${env.PORT}/oauth/callback`],
54 ['scope', `atproto transition:generic`],
55 ])}`,
56 )
57
58 return new NodeOAuthClient({
59 keyset,
60 clientMetadata,
61 stateStore: new StateStore(db),
62 sessionStore: new SessionStore(db),
63 plcDirectoryUrl: env.PLC_URL,
64 handleResolver: env.PDS_URL,
65 })
66}