Skip to content

Commit

Permalink
Add support for external DID resolver.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Mar 17, 2024
1 parent 3e8cb43 commit 9a9af4f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# bedrock-vc-verifier ChangeLog

## 19.2.0 - 2024-mm-dd

### Added
- Add support for using a configured external DID resolver.

## 19.1.0 - 2024-01-25

### Changed
Expand Down
73 changes: 70 additions & 3 deletions lib/documentLoader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2019-2024 Digital Bazaar, Inc. All rights reserved.
*/
import * as bedrock from '@bedrock/core';
import {
Expand All @@ -9,6 +9,7 @@ import {
} from '@bedrock/jsonld-document-loader';
import {createContextDocumentLoader} from '@bedrock/service-context-store';
import {didIo} from '@bedrock/did-io';
import {klona} from 'klona';
import '@bedrock/credentials-context';
import '@bedrock/data-integrity-context';
import '@bedrock/did-context';
Expand Down Expand Up @@ -53,9 +54,17 @@ export async function createDocumentLoader({config} = {}) {
{config, serviceType});

return async function documentLoader(url) {
// resolve all DID URLs through did-io
// handle DID URLs...
if(url.startsWith('did:')) {
const document = await didIo.get({url});
let document;
if(config.verifyOptions?.didResolver) {
// resolve via configured DID resolver
const {verifyOptions: {didResolver}} = config;
document = await _resolve({didResolver, didUrl: url});
} else {
// resolve via did-io
document = await didIo.get({url});
}
return {
contextUrl: null,
documentUrl: url,
Expand Down Expand Up @@ -83,3 +92,61 @@ export async function createDocumentLoader({config} = {}) {
}
};
}

async function _resolve({didResolver, didUrl}) {
// split on `?` query or `#` fragment
const [did] = didUrl.split(/(?=[\?#])/);

// fetch DID document using DID resolver;
// assume a universal DID resolver 1.0 API in this version
const url = `${didResolver.url}/1.0/identifiers/${encodeURIComponent(did)}`;
const data = await httpClientHandler.get({url});

if(data?.id !== did) {
throw new Error(`DID document for DID "${did}" not found.`);
}

// FIXME: perform DID document validation
// FIXME: handle URL query param / services
const didDocument = data;

// if a fragment was found use the fragment to dereference a subnode
// in the did doc
const [, fragment] = url.split('#');
if(fragment) {
const id = `${didDocument.id}${fragment}`;
return _getNode({didDocument, id});
}
// resolve the full DID Document
return didDocument;
}

function _getNode({didDocument, id}) {
// do verification method search first
let match = didDocument?.verificationMethod?.find(vm => vm?.id === id);
if(!match) {
// check other top-level nodes
for(const [key, value] of Object.entries(didDocument)) {
if(key === '@context' || key === 'verificationMethod') {
continue;
}
if(Array.isArray(value)) {
match = value.find(e => e?.id === id);
} else if(value?.id === id) {
match = value;
}
if(match) {
break;
}
}
}

if(!match) {
throw new Error(`DID document entity with id "${id}" not found.`);
}

return {
'@context': klona(didDocument['@context']),
...klona(match)
};
}
17 changes: 15 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
/*!
* Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
*/
import * as bedrock from '@bedrock/core';
import {createService, schemas} from '@bedrock/service-core';
import {
addRoutes as addContextStoreRoutes
} from '@bedrock/service-context-store';
import {addRoutes} from './http.js';
import {createService} from '@bedrock/service-core';
import {initializeServiceAgent} from '@bedrock/service-agent';
import {klona} from 'klona';
import {verifyOptions} from '../schemas/bedrock-vc-verifier.js';

// load config defaults
import './config.js';

const serviceType = 'vc-verifier';

bedrock.events.on('bedrock.init', async () => {
// add customizations to config validators...
const createConfigBody = klona(schemas.createConfigBody);
const updateConfigBody = klona(schemas.updateConfigBody);
const schemasToUpdate = [createConfigBody, updateConfigBody];
for(const schema of schemasToUpdate) {
// verify options
schema.properties.verifyOptions = verifyOptions;
}

// create `vc-verifier` service
const service = await createService({
serviceType,
Expand All @@ -24,6 +35,8 @@ bedrock.events.on('bedrock.init', async () => {
revocation: 1
},
validation: {
createConfigBody,
updateConfigBody,
// require these zcaps (by reference ID)
zcapReferenceIds: [{
referenceId: 'edv',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"bnid": "^3.0.0",
"body-parser": "^1.20.0",
"cors": "^2.8.5",
"klona": "^2.0.6",
"serialize-error": "^11.0.0"
},
"peerDependencies": {
Expand Down
21 changes: 20 additions & 1 deletion schemas/bedrock-vc-verifier.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
*/
const context = {
title: '@context',
Expand All @@ -10,6 +10,25 @@ const context = {
}
};

export const verifyOptions = {
title: 'Verify Options',
type: 'object',
required: ['didResolver'],
additionalProperties: false,
properties: {
didResolver: {
title: 'DID Resolver',
type: 'object',
required: ['url'],
additionalProperties: false,
url: {
type: 'string',
pattern: '^https://[^.]+.[^.]+'
}
}
}
};

export const createChallengeBody = {
title: 'Create Challenge Body',
type: 'object',
Expand Down

0 comments on commit 9a9af4f

Please sign in to comment.