A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 2.6 kB View raw
1/** 2 * Resolve a writer's identity for the read-through renderer (SP4). 3 * Runs server-side (Node/edge): handle → DID → PDS. 4 */ 5import { resolvePdsUrl } from '../media/pds'; 6import { safeFetch } from '../net/safe-fetch'; 7import { isValidHandleOrDid, normalizeHandle } from '../auth/config'; 8 9/** Default handle resolver — a `com.atproto.identity.resolveHandle` XRPC endpoint. */ 10const FALLBACK_RESOLVER = 'https://bsky.social'; 11 12async function resolveViaWellKnown( handle: string ): Promise< string | null > { 13 try { 14 // safeFetch rejects non-public hosts, so an attacker-supplied handle like 15 // `169.254.169.254` or `internal-svc` can't trigger an internal request (SSRF). 16 const res = await safeFetch( `https://${ handle }/.well-known/atproto-did` ); 17 if ( ! res.ok ) { 18 return null; 19 } 20 const did = ( await res.text() ).trim(); 21 return did.startsWith( 'did:' ) ? did : null; 22 } catch { 23 return null; 24 } 25} 26 27async function resolveViaXrpc( handle: string ): Promise< string | null > { 28 try { 29 const res = await safeFetch( 30 `${ FALLBACK_RESOLVER }/xrpc/com.atproto.identity.resolveHandle?handle=${ encodeURIComponent( 31 handle 32 ) }` 33 ); 34 if ( ! res.ok ) { 35 return null; 36 } 37 const data: { did?: string } = await res.json(); 38 return data.did ?? null; 39 } catch { 40 return null; 41 } 42} 43 44/** 45 * Resolve a handle (or DID) to a DID. Prefers the handle's own `.well-known` 46 * (no third party), falling back to a public resolver XRPC. 47 */ 48export async function resolveHandleToDid( handleOrDid: string ): Promise< string | null > { 49 // Validate before the value is used as a resolver fetch host: the read path takes it 50 // straight from the URL (`/@<author>`), so a non-handle-shaped value could otherwise 51 // smuggle a path/port/query into the outbound request and turn the worker into a 52 // request proxy. safeFetch still guards internal hosts; this rejects bad syntax first. 53 if ( ! isValidHandleOrDid( handleOrDid ) ) { 54 return null; 55 } 56 if ( handleOrDid.startsWith( 'did:' ) ) { 57 return handleOrDid; 58 } 59 const handle = normalizeHandle( handleOrDid ); 60 return ( await resolveViaWellKnown( handle ) ) ?? ( await resolveViaXrpc( handle ) ); 61} 62 63export interface Author { 64 did: string; 65 pdsUrl: string; 66} 67 68/** Resolve a handle/DID to `{ did, pdsUrl }`, or null if it can't be resolved. */ 69export async function resolveAuthor( handleOrDid: string ): Promise< Author | null > { 70 const did = await resolveHandleToDid( handleOrDid ); 71 if ( ! did ) { 72 return null; 73 } 74 try { 75 return { did, pdsUrl: await resolvePdsUrl( did ) }; 76 } catch { 77 return null; 78 } 79}