import { Client } from '@atcute/client'; import type { ActorResolver } from '@atcute/identity-resolver'; import type { Did } from '@atcute/lexicons'; import { configureOAuth, createAuthorizationUrl, deleteStoredSession, finalizeAuthorization, getSession, OAuthUserAgent, type ClientAssertionFetcher, type Session as OAuthSession, } from '@atcute/oauth-browser-client'; import { scopes } from './metadata.ts'; import { config, resolveActor } from './identity.ts'; import type { Session } from './session.ts'; export { scopes, clientMetadata, type ClientMetadata } from './metadata.ts'; export interface AuthOptions { clientId: string; redirectUri: string; /** Signs client assertions on the API worker, which makes nibling a confidential client (D19). */ fetchClientAssertion?: ClientAssertionFetcher; } export function configureAuth({ clientId, redirectUri, fetchClientAssertion }: AuthOptions): void { configureOAuth({ metadata: { client_id: clientId, redirect_uri: redirectUri }, identityResolver, ...(fetchClientAssertion ? { fetchClientAssertion } : {}), }); } /** Sends the browser to the user's authorization server. The page unloads on success. */ export async function signIn(identifier: string): Promise { const actor = await resolveActor(identifier); const url = await createAuthorizationUrl({ target: { type: 'account', identifier: actor.handle ?? actor.did }, scope: scopes, }); window.location.assign(url); } /** Exchanges the callback's query or fragment parameters for a session. */ export async function completeSignIn(params: URLSearchParams): Promise { const { session } = await finalizeAuthorization(params); return toSession(session); } /** The stored session for `did`, refreshed if needed, or undefined if there is none or it was revoked. */ export async function resume(did: Did): Promise { try { return toSession(await getSession(did, { allowStale: true })); } catch { return undefined; } } export async function signOut(did: Did): Promise { if (config.viewer === did) config.viewer = null; try { await new OAuthUserAgent(await getSession(did, { allowStale: true })).signOut(); } catch { deleteStoredSession(did); } } /** Also marks the account as the viewer, so reads of its own repo skip the gateway's cache. */ function toSession(session: OAuthSession): Session { config.viewer = session.info.sub; return { did: session.info.sub, client: new Client({ handler: new OAuthUserAgent(session) }) }; } const identityResolver: ActorResolver = { async resolve(identifier) { const actor = await resolveActor(identifier); return { did: actor.did, handle: actor.handle ?? 'handle.invalid', pds: actor.pds }; }, };