UNPKG

did-resolver

Version:
161 lines • 5.96 kB
"use strict"; // Copyright 2018 Consensys AG Object.defineProperty(exports, "__esModule", { value: true }); exports.Resolver = exports.VerificationRelationship = void 0; exports.inMemoryCache = inMemoryCache; exports.noCache = noCache; exports.parse = parse; exports.wrapLegacyResolver = wrapLegacyResolver; /** * Represents the Verification Relationship between a DID subject and a Verification Method. * * @see {@link https://www.w3.org/TR/did-core/#verification-relationships} */ exports.VerificationRelationship = { authentication: 'authentication', assertionMethod: 'assertionMethod', keyAgreement: 'keyAgreement', capabilityInvocation: 'capabilityInvocation', capabilityDelegation: 'capabilityDelegation', }; function inMemoryCache() { const cache = new Map(); return async (parsed, resolve, options) => { if (options?.cache === false) return await resolve(); const cached = cache.get(parsed.didUrl); if (cached !== undefined) return cached; const result = await resolve(); if (result.didResolutionMetadata?.error !== 'notFound') { cache.set(parsed.didUrl, result); } return result; }; } function noCache(parsed, resolve) { return resolve(); } // RFC 3986 ABNF components (referenced by DID Core v1.0 §3.2) const HEXDIG = '[0-9a-fA-F]'; // allows both lowercase and uppercase const PCT_ENCODED = `(?:%${HEXDIG}{2})`; // pct-encoded = "%" HEXDIG HEXDIG // DID Core v1.0 §3.1 const ID_CHAR = `(?:[a-zA-Z0-9._-]|${PCT_ENCODED})`; // idchar const METHOD = '[a-z0-9]+'; // method-name = 1*(%x61-7A / DIGIT) const METHOD_ID = `(?:${ID_CHAR}*:)*${ID_CHAR}+`; // method-specific-id // DID Core v1.0 §3.2 tail via RFC 3986 §3.3 const UNRESERVED = '[a-zA-Z0-9._~-]'; const SUB_DELIMS = "[!$&'()*+,;=]"; const PCHAR = `(?:${UNRESERVED}|${PCT_ENCODED}|${SUB_DELIMS}|[:@])`; // pchar const PATH_ABEMPTY = `(?:/${PCHAR}*)*`; // path-abempty const QUERY_CHARS = `(?:${PCHAR}|[/?])*`; // query const FRAGMENT_CHARS = `(?:${PCHAR}|[/?])*`; // fragment const DID_URL_MATCHER = new RegExp(`^did:(${METHOD}):(${METHOD_ID})(${PATH_ABEMPTY})(?:\\?(${QUERY_CHARS}))?(?:#(${FRAGMENT_CHARS}))?$`); /** * Parses a DID URL strictly according to the DID Core v1.0 specification ABNF * (§3.1 DID Syntax, §3.2 DID URL Syntax). Unlike {@link parse}, this method * does not accept legacy DID parameters (`;key=value`) and enforces the * RFC 3986 character sets for path, query, and fragment components. * * @param didUrl - the DID URL string to be parsed * @returns a ParsedDID object, or null if the input does not conform to the spec */ function parse(didUrl) { if (!didUrl) return null; const m = didUrl.match(DID_URL_MATCHER); if (!m) return null; const parsed = { did: `did:${m[1]}:${m[2]}`, method: m[1], id: m[2], didUrl, }; // path-abempty always matches (possibly empty); only attach a real path. if (m[3]) parsed.path = m[3]; // query/fragment groups are `undefined` when absent, `''` when present but empty // (`did:x:y?` / `did:x:y#`), preserve the distinction. if (m[4] !== undefined) parsed.query = m[4]; if (m[5] !== undefined) parsed.fragment = m[5]; // params are intentionally omitted (matrix parameters not in DID Core v1.0 spec) return parsed; } const EMPTY_RESULT = { didResolutionMetadata: {}, didDocument: null, didDocumentMetadata: {}, }; function wrapLegacyResolver(resolve) { return async (did, parsed, resolver) => { try { const doc = await resolve(did, parsed, resolver); return { ...EMPTY_RESULT, didResolutionMetadata: { contentType: 'application/did+ld+json' }, didDocument: doc, }; } catch (e) { return { ...EMPTY_RESULT, didResolutionMetadata: { error: 'notFound', message: String(e), // This is not in spec, but may be helpful }, }; } }; } /** * This implementation of {@link Resolvable} bundles together multiple implementations of {@link DIDResolver} and * presents a single function call to users. */ class Resolver { registry; cache; constructor(registry = {}, options = {}) { this.registry = registry; this.cache = options.cache === true ? inMemoryCache() : options.cache === false ? noCache : options.cache || noCache; if (options.legacyResolvers) { Object.keys(options.legacyResolvers).map((methodName) => { if (!this.registry[methodName]) { this.registry[methodName] = wrapLegacyResolver(options.legacyResolvers[methodName]); } }); } } async resolve(didUrl, options = {}) { let parsed = parse(didUrl); if (parsed === null) { return { ...EMPTY_RESULT, didResolutionMetadata: { error: 'invalidDid' }, }; } const resolver = this.registry[parsed.method]; if (!resolver) { return { ...EMPTY_RESULT, didResolutionMetadata: { error: 'unsupportedDidMethod' }, }; } // Use resolver's method-specific parser if available for additional validation if (resolver.parser) { const refined = resolver.parser(parsed); if (refined === null) { return { ...EMPTY_RESULT, didResolutionMetadata: { error: 'invalidDid' }, }; } parsed = refined; } return this.cache(parsed, () => resolver(parsed.did, parsed, this, options), options); } } exports.Resolver = Resolver; //# sourceMappingURL=index.js.map