UNPKG

@atproto/did

Version:

DID resolution and verification library

116 lines 4.3 kB
import { z } from 'zod'; import { DidError, InvalidDidError } from './did-error.js'; import { isDidRefAbsolute } from './did-ref.js'; import { canParse } from './lib/uri.js'; import { DID_PLC_PREFIX, DID_WEB_PREFIX, assertDidPlc, assertDidWeb, isDidPlc, isDidWeb, } from './methods.js'; import { matchesIdentifier } from './utils.js'; export const atprotoDidSchema = z .string() .refine(isAtprotoDid, `Atproto only allows "plc" and "web" DID methods`); export function isAtprotoDid(input) { return isDidPlc(input) || isAtprotoDidWeb(input); } export function asAtprotoDid(input) { assertAtprotoDid(input); return input; } export function assertAtprotoDid(input) { if (typeof input !== 'string') { throw new InvalidDidError(typeof input, `DID must be a string`); } else if (input.startsWith(DID_PLC_PREFIX)) { assertDidPlc(input); } else if (input.startsWith(DID_WEB_PREFIX)) { assertAtprotoDidWeb(input); } else { throw new InvalidDidError(input, `Atproto only allows "plc" and "web" DID methods`); } } export function assertAtprotoDidWeb(input) { assertDidWeb(input); if (isDidWebWithPath(input)) { throw new InvalidDidError(input, `Atproto does not allow path components in Web DIDs`); } if (isDidWebWithHttpsPort(input)) { throw new InvalidDidError(input, `Atproto does not allow port numbers in Web DIDs, except for localhost`); } } /** * @see {@link https://atproto.com/specs/did#blessed-did-methods} */ export function isAtprotoDidWeb(input) { if (!isDidWeb(input)) { return false; } if (isDidWebWithPath(input)) { return false; } if (isDidWebWithHttpsPort(input)) { return false; } return true; } function isDidWebWithPath(did) { return did.includes(':', DID_WEB_PREFIX.length); } function isLocalhostDid(did) { return (did === 'did:web:localhost' || did.startsWith('did:web:localhost:') || did.startsWith('did:web:localhost%3A')); } function isDidWebWithHttpsPort(did) { if (isLocalhostDid(did)) return false; const pathIdx = did.indexOf(':', DID_WEB_PREFIX.length); const hasPort = pathIdx === -1 ? // No path component, check if there's a port separator anywhere after // the "did:web:" prefix did.includes('%3A', DID_WEB_PREFIX.length) : // There is a path component; if there is an encoded colon *before* it, // then there is a port number did.lastIndexOf('%3A', pathIdx) !== -1; return hasPort; } export function extractAtprotoData(document) { return { did: document.id, aka: document.alsoKnownAs?.find(isAtprotoAka)?.slice(5), key: document.verificationMethod?.find((isAtprotoVerificationMethod), document), pds: document.service?.find((isAtprotoPersonalDataServerService), document), }; } export function extractPdsUrl(document) { const service = document.service?.find(isAtprotoPersonalDataServerService, document); if (!service) { throw new DidError(document.id, `Document ${document.id} does not contain a (valid) #atproto_pds service URL`, 'did-service-not-found'); } return new URL(service.serviceEndpoint); } export function isAtprotoAka(value) { return value.startsWith('at://'); } export function isAtprotoPersonalDataServerService(service) { return (service?.type === 'AtprotoPersonalDataServer' && typeof service.serviceEndpoint === 'string' && canParse(service.serviceEndpoint) && matchesIdentifier(this.id, 'atproto_pds', service.id)); } export const ATPROTO_VERIFICATION_METHOD_TYPES = Object.freeze([ 'EcdsaSecp256r1VerificationKey2019', 'EcdsaSecp256k1VerificationKey2019', 'Multikey', ]); export function isAtprotoVerificationMethod(method) { return (typeof method === 'object' && typeof method?.publicKeyMultibase === 'string' && ATPROTO_VERIFICATION_METHOD_TYPES.includes(method.type) && matchesIdentifier(this.id, 'atproto', method.id)); } export function isAtprotoDidRefAbsolute(value) { if (!isDidRefAbsolute(value)) return false; return isAtprotoDid(value.slice(0, value.indexOf('#'))); } //# sourceMappingURL=atproto.js.map