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