diff --git a/packages/dids/src/did-resolver.ts b/packages/dids/src/did-resolver.ts index 22beaf427..8cd5fa6b4 100644 --- a/packages/dids/src/did-resolver.ts +++ b/packages/dids/src/did-resolver.ts @@ -3,7 +3,8 @@ import type { DidMethodResolver, DidResolutionResult, DidResolutionOptions, - DidResource + DidResource, + DidDereferenceResult } from './types.js'; import { parseDid } from './utils.js'; @@ -14,6 +15,9 @@ export type DidResolverOptions = { cache?: DidResolverCache; } +/** + * argument passed to {@link DidResolver.resolve} + */ export type DereferenceParams = { didUrl: string } @@ -112,44 +116,80 @@ export class DidResolver { } /** - * Asynchronously dereferences a DID (Decentralized Identifier) URL to a corresponding DID resource. This method interprets the DID URL's components, which include the DID method, method-specific identifier, path, query, and fragment, - * and retrieves the related resource as per the DID Core specifications. The dereferencing process involves resolving the DID contained in the DID URL to a DID document, and then extracting the specific part - * of the document identified by the fragment in the DID URL. If no fragment is specified, the entire DID document is returned. This method supports resolution of different components within a DID document such - * as service endpoints and verification methods, based on their IDs. It accommodates both full and relative DID URLs as specified in the DID Core specification. + * dereferences a DID (Decentralized Identifier) URL to a corresponding DID resource. This method interprets + * the DID URL's components, which include the DID method, method-specific identifier, path, query, and fragment, + * and retrieves the related resource as per the DID Core specifications. The dereferencing process involves resolving + * the DID contained in the DID URL to a DID document, and then extracting the specific part of the document identified + * by the fragment in the DID URL. If no fragment is specified, the entire DID document is returned. * - * More information on DID URL dereferencing can be found in the DID Core specification [here](https://www.w3.org/TR/did-core/#did-url-dereferencing) + * This method supports resolution of different components within a DID document such as + * service endpoints and verification methods, based on their IDs. It accommodates both full and relative DID URLs + * as specified in the DID Core specification. + * + * More information on DID URL dereferencing can be found in the DID Core specification + * [here](https://www.w3.org/TR/did-core/#did-url-dereferencing) * * @param params - An object of type `DereferenceParams` containing the `didUrl` which needs to be dereferenced. - * @returns A `Promise` that resolves to a `DidResource`, which can be the entire DID Document or a specific part of it (like a verification method or service endpoint) depending on the presence and value of the fragment in the DID URL. + * @returns a {@link DidDereferenceResult} */ - async dereference(params: DereferenceParams): Promise { + async dereference(params: DereferenceParams): Promise { const { didUrl } = params; - const { didDocument } = await this.resolve(didUrl); + const { didDocument, didResolutionMetadata, didDocumentMetadata } = await this.resolve(didUrl); + + if (didResolutionMetadata.error) { + return { + dereferencingMetadata : didResolutionMetadata, + contentStream : null, + contentMetadata : didDocumentMetadata + }; + } const parsedDid = parseDid(params); // return the entire DID Document if no fragment is present on the did url if (!parsedDid.fragment) { - return didDocument; + return { + dereferencingMetadata : didResolutionMetadata, + contentStream : didDocument, + contentMetadata : didDocumentMetadata + }; } - const { service, verificationMethod } = didDocument; + const { service = [], verificationMethod = [] } = didDocument; // create a set of possible id matches. the DID spec allows for an id to be the entire did#fragment or just #fragment. // See: https://www.w3.org/TR/did-core/#relative-did-urls // using a set for fast string comparison. DIDs can be lonnng. const idSet = new Set([didUrl, parsedDid.fragment, `#${parsedDid.fragment}`]); + let didResource: DidResource; for (let vm of verificationMethod) { if (idSet.has(vm.id)) { - return vm; + didResource = vm; + break; } } for (let svc of service) { if (idSet.has(svc.id)) { - return svc; + didResource = svc; + break; } } + if (didResource) { + return { + dereferencingMetadata : didResolutionMetadata, + contentStream : didResource, + contentMetadata : didResolutionMetadata + }; + } else { + return { + dereferencingMetadata: { + error: 'notFound' + }, + contentStream : null, + contentMetadata : {}, + }; + } } } \ No newline at end of file diff --git a/packages/dids/src/types.ts b/packages/dids/src/types.ts index 9d569d153..097315afb 100644 --- a/packages/dids/src/types.ts +++ b/packages/dids/src/types.ts @@ -183,6 +183,35 @@ export type DidResolutionResult = { didDocumentMetadata: DidDocumentMetadata }; +export type DidDereferenceResult = { + /** + * A metadata structure consisting of values relating to the results of the DID URL dereferencing process. + * This structure is REQUIRED, and in the case of an error in the dereferencing process, this MUST NOT be empty. + * Properties defined by this specification are in 7.2.2 DID URL Dereferencing Metadata. If the dereferencing is + * not successful, this structure MUST contain an error property describing the error. + */ + dereferencingMetadata: DidResolutionMetadata + /** + * If the dereferencing function was called and successful, this MUST contain a resource corresponding to the DID URL. + * The contentStream MAY be a resource such as: + * * A DID document that is serializable in one of the conformant representations + * * A Verification Method, + * * A service + * * Any other resource format that can be identified via a Media Type and obtained through the resolution process. + * + * If the dereferencing is unsuccessful, this value MUST be empty. + */ + contentStream: DidResource | null + /** + * If the dereferencing is successful, this MUST be a metadata structure, but the structure MAY be empty. + * This structure contains metadata about the contentStream. If the contentStream is a DID document, + * this MUST be a didDocumentMetadata structure as described in DID Resolution. If the dereferencing is unsuccessful, + * this output MUST be an empty metadata structure. + + */ + contentMetadata: DidDocumentMetadata +} + /** * implement this interface to provide your own cache for did resolution results. can be plugged in through Web5 API */ diff --git a/packages/dids/tests/did-resolver.spec.ts b/packages/dids/tests/did-resolver.spec.ts index 914e5a49d..1b182c63c 100644 --- a/packages/dids/tests/did-resolver.spec.ts +++ b/packages/dids/tests/did-resolver.spec.ts @@ -6,6 +6,7 @@ import { DidResolver } from '../src/did-resolver.js'; import { didResolverTestVectors } from './fixtures/test-vectors/did-resolver.js'; import { DidResolverCacheLevel } from '../src/resolver-cache-level.js'; import { DidResolverCache } from '../src/types.js'; +import { isVerificationMethod } from '../src/utils.js'; describe('DidResolver', () => { describe('resolve()', () => { @@ -114,4 +115,53 @@ describe('DidResolver', () => { }); }); }); + + describe('dereference()', () => { + let didResolver: DidResolver; + + beforeEach(() => { + const didMethodApis = [DidKeyMethod]; + didResolver = new DidResolver({ didResolvers: didMethodApis }); + }); + + it('returns a result with contentStream set to null and dereferenceMetadata.error set if resolution fails', async () => { + const result = await didResolver.dereference({ didUrl: 'abcd123;;;' }); + expect(result.contentStream).to.be.null; + expect(result.dereferencingMetadata.error).to.exist; + expect(result.dereferencingMetadata.error).to.equal('invalidDid'); + + }); + + it('returns the appropriate DID resource as the value of contentStream if found', async () => { + const did = await DidKeyMethod.create(); + + const result = await didResolver.dereference({ didUrl: did.document.verificationMethod[0].id }); + expect(result.contentStream).to.be.not.be.null; + expect(result.dereferencingMetadata.error).to.not.exist; + + const didResource = result.contentStream; + expect(isVerificationMethod(didResource)).to.be.true; + }); + + it('returns the entire did document as the value of contentStream if the did url contains no fragment', async () => { + const did = await DidKeyMethod.create(); + + const result = await didResolver.dereference({ didUrl: did.did }); + expect(result.contentStream).to.be.not.be.null; + expect(result.dereferencingMetadata.error).to.not.exist; + + const didResource = result.contentStream; + expect(didResource['@context']).to.exist; + expect(didResource['@context']).to.include('https://www.w3.org/ns/did/v1'); + }); + + it('returns contentStream set to null and dereferenceMetadata.error set to notFound if resource is not found', async () => { + const did = await DidKeyMethod.create(); + + const result = await didResolver.dereference({ didUrl: `${did.did}#0` }); + expect(result.contentStream).to.be.null; + expect(result.dereferencingMetadata.error).to.exist; + expect(result.dereferencingMetadata.error).to.equal('notFound'); + }); + }); }); \ No newline at end of file