import { SdkError } from './errors.ts'; /** The scopes in D19: our collections, blobs, and the service-auth token a tangled push needs. */ export const scopes = [ 'atproto', 'repo:dev.nibling.project', 'repo:dev.nibling.feed.like', 'repo:dev.nibling.feed.comment', 'repo:dev.nibling.graph.follow', 'repo:dev.nibling.actor.profile', 'blob:*/*', 'rpc:org.tangled.git.receivePack?aud=*', ].join(' '); const CALLBACK_PATH = '/oauth/callback'; export interface ClientMetadata { client_id: string; client_name?: string; client_uri?: string; redirect_uris: [string]; scope: string; grant_types: ['authorization_code', 'refresh_token']; response_types: ['code']; token_endpoint_auth_method: 'none'; application_type: 'web'; dpop_bound_access_tokens: true; } /** * The client-metadata.json document for `origin`, served at `/oauth/client-metadata.json`. * A loopback origin (`http://127.0.0.1:`) gets atproto's `http://localhost` client ID instead, which needs no hosted document. */ export function clientMetadata({ origin, name }: { origin: string; name: string }): ClientMetadata { const url = new URL(origin); const redirect = `${url.origin}${CALLBACK_PATH}`; const common = { redirect_uris: [redirect] as [string], scope: scopes, grant_types: ['authorization_code', 'refresh_token'] as ['authorization_code', 'refresh_token'], response_types: ['code'] as ['code'], token_endpoint_auth_method: 'none' as const, application_type: 'web' as const, dpop_bound_access_tokens: true as const, }; if (url.protocol === 'http:') { if (url.hostname !== '127.0.0.1' && url.hostname !== '[::1]') { throw new SdkError( `OAuth loopback clients must be served from http://127.0.0.1, not ${url.origin}`, 'ERR_NIB_AUTH_LOOPBACK', ); } const query = new URLSearchParams({ redirect_uri: redirect, scope: scopes }); return { client_id: `http://localhost?${query}`, ...common }; } return { client_id: `${url.origin}/oauth/client-metadata.json`, client_name: name, client_uri: url.origin, ...common }; }