Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159import { isDid, isHandle } from '@atcute/lexicons/syntax';import { didDocumentUrl, serviceEndpoint } from '../identity.ts';import { isPublicHttpsUrl } from '../net.ts';import { cached, POLICY } from './cache.ts';import { cacheKey, type Gateway } from './context.ts';import { CORS, failureResponse, fetchUpstream, MAX_JSON_BYTES, readLimited, UpstreamError, xrpcError } from './proxy.ts';
/** `blue.microcosm.identity.resolveMiniDoc`'s answer. `handle` is `handle.invalid` when it does not * resolve back to the DID. */export interface MiniDoc { did: string; handle: string; pds: string;}
type Found<T> = { found: true; value: T } | { found: false; message: string };
const json = (body: unknown) => Response.json(body, { headers: CORS });
async function readJson(res: Response): Promise<unknown> { return JSON.parse(new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES)));}
/** `com.atproto.identity.resolveDid`, cached for 5 minutes. Straight from PLC or the did:web host. */export function resolveDid(g: Gateway, did: string, bypass = false): Promise<Response> { return cached(g, cacheKey(g, '/xrpc/com.atproto.identity.resolveDid', { did }), POLICY.identity, bypass, async () => { const url = didDocumentUrl(did); if (!url) return xrpcError('DidNotFound', `${did} is not a did:plc or a public did:web`, 400); try { const res = await fetchUpstream(g.fetch, url, { headers: { accept: 'application/json' } }); if (res.status === 404 || res.status === 410) { await res.body?.cancel(); return xrpcError('DidNotFound', `no DID document for ${did}`, 400); } if (!res.ok) { await res.body?.cancel(); throw new UpstreamError('unreachable', `${new URL(url).host} answered ${res.status} for ${did}`); } const doc = await readJson(res); if ((doc as { id?: unknown } | null)?.id !== did) return xrpcError('DidNotFound', `the document at ${url} is not ${did}'s`, 400); return json({ didDoc: doc }); } catch (error) { return error instanceof SyntaxError ? xrpcError('UpstreamFailure', `${did}'s document is not JSON`, 502) : failureResponse(error); } });}
/** A DID document through `resolveDid`'s cache. Throws `UpstreamError` when that is an outage. */export async function didDocument(g: Gateway, did: string): Promise<Found<unknown>> { const res = await resolveDid(g, did); if (res.status >= 500) { await res.body?.cancel(); throw new UpstreamError('unreachable', `could not read ${did}'s DID document`); } const body = (await res.json()) as { didDoc?: unknown; message?: string }; return res.ok ? { found: true, value: body.didDoc } : { found: false, message: body.message ?? `no DID document for ${did}` };}
/** `_atproto.<handle>` over DNS-over-HTTPS, then `https://<handle>/.well-known/atproto-did`. */async function resolveHandle(g: Gateway, handle: string): Promise<string | undefined> { try { const res = await fetchUpstream(g.fetch, `https://cloudflare-dns.com/dns-query?name=_atproto.${handle}&type=TXT`, { headers: { accept: 'application/dns-json' }, }); if (res.ok) { const answers = ((await readJson(res)) as { Answer?: { data?: unknown }[] }).Answer ?? []; const dids = answers.flatMap((a) => { const match = typeof a.data === 'string' ? /^"?did=([^"\s]+)"?$/.exec(a.data) : null; return match && isDid(match[1]) ? [match[1]] : []; }); if (dids.length === 1) return dids[0]; } else { await res.body?.cancel(); } } catch { // DNS unanswered: try the well-known file. } const wellKnown = `https://${handle}/.well-known/atproto-did`; if (!isPublicHttpsUrl(wellKnown)) return undefined; try { const res = await fetchUpstream(g.fetch, wellKnown); if (!res.ok) { await res.body?.cancel(); return undefined; } const did = new TextDecoder().decode(await readLimited(res, 2048)).trim(); return isDid(did) ? did : undefined; } catch { return undefined; }}
function handleClaim(doc: unknown): string | undefined { const aka = (doc as { alsoKnownAs?: unknown } | null)?.alsoKnownAs; const first = Array.isArray(aka) ? aka.find((v): v is string => typeof v === 'string' && v.startsWith('at://')) : undefined; const handle = first?.slice('at://'.length).toLowerCase(); return handle && isHandle(handle) ? handle : undefined;}
async function viaSlingshot(g: Gateway, identifier: string): Promise<MiniDoc | undefined> { try { const url = new URL('/xrpc/blue.microcosm.identity.resolveMiniDoc', g.slingshot); url.searchParams.set('identifier', identifier); const res = await fetchUpstream(g.fetch, url.toString(), { headers: { accept: 'application/json' } }); if (!res.ok) { await res.body?.cancel(); return undefined; } const body = (await readJson(res)) as Partial<MiniDoc>; if (typeof body.did !== 'string' || typeof body.handle !== 'string' || typeof body.pds !== 'string') return undefined; if (isDid(identifier) ? body.did !== identifier : body.handle.toLowerCase() !== identifier) return undefined; if (!isPublicHttpsUrl(body.pds)) return undefined; return { did: body.did, handle: body.handle, pds: body.pds }; } catch { return undefined; }}
/** The same answer built from PLC or did:web and DNS, checking the handle both ways. */async function viaDocument(g: Gateway, identifier: string): Promise<Found<MiniDoc>> { const did = isDid(identifier) ? identifier : await resolveHandle(g, identifier); if (!did) return { found: false, message: `${identifier} does not resolve to a DID` }; const doc = await didDocument(g, did); if (!doc.found) return doc; const pds = serviceEndpoint(doc.value, did, { id: '#atproto_pds' }); if (!pds) return { found: false, message: `${did} names no public https PDS` }; const claimed = handleClaim(doc.value); const verified = isDid(identifier) ? claimed !== undefined && (await resolveHandle(g, claimed)) === did : claimed === identifier; return { found: true, value: { did, handle: verified ? claimed! : 'handle.invalid', pds } };}
/** `blue.microcosm.identity.resolveMiniDoc`, cached for 5 minutes: Slingshot first, then PLC or * did:web and DNS directly. `identifier` is already a parsed DID or lowercase handle. */export function resolveMiniDoc(g: Gateway, identifier: string, bypass = false): Promise<Response> { return cached(g, cacheKey(g, '/xrpc/blue.microcosm.identity.resolveMiniDoc', { identifier }), POLICY.identity, bypass, async () => { const slingshot = await viaSlingshot(g, identifier); if (slingshot) return json(slingshot); try { const direct = await viaDocument(g, identifier); return direct.found ? json(direct.value) : xrpcError('NotFound', direct.message, 400); } catch (error) { return failureResponse(error); } });}
/** Who `identifier` is and where their repo lives, through `resolveMiniDoc`'s cache. */export async function miniDoc(g: Gateway, identifier: string): Promise<Found<MiniDoc>> { const res = await resolveMiniDoc(g, identifier); if (res.status >= 500) { await res.body?.cancel(); throw new UpstreamError('unreachable', `could not resolve ${identifier}`); } const body = (await res.json()) as MiniDoc & { message?: string }; return res.ok ? { found: true, value: body } : { found: false, message: body.message ?? `could not resolve ${identifier}` };}