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 assert( 16 !env.PUBLIC_URL || env.PRIVATE_JWKS, 17 'ATProto requires backend clients to be confidential', 18 ) 19 20 // Confidential client require a keyset accessible on the internet. Non 21 // internet clients (e.g. development) cannot expose a keyset on the internet 22 // so they can't be private.. 23 const keyset = 24 env.PUBLIC_URL && env.PRIVATE_JWKS 25 ? new Keyset( 26 await Promise.all( 27 env.PRIVATE_JWKS.map((jwk) => JoseKey.fromJWK(jwk)), 28 ), 29 ) 30 : undefined 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 no 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 64 // XXX Staging 65 plcDirectoryUrl: 'https://plc.staging.bsky.dev', 66 handleResolver: 'https://staging.bsky.dev', 67 }) 68}