From 74f3911234e8cdc33177e55b17c710c658dcfc20 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 29 Jul 2022 14:44:50 -0400 Subject: [PATCH 01/55] Start work on did & did:key errors & validation. --- lib/didComponents.js | 31 ++++++++++++++++++ lib/didErrors.js | 21 ++++++++++++ lib/didKeyErrors.js | 37 +++++++++++++++++++++ lib/helpers.js | 3 ++ lib/main.js | 2 +- lib/validators.js | 77 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 lib/didComponents.js create mode 100644 lib/didErrors.js create mode 100644 lib/didKeyErrors.js create mode 100644 lib/helpers.js create mode 100644 lib/validators.js diff --git a/lib/didComponents.js b/lib/didComponents.js new file mode 100644 index 0000000..18e0115 --- /dev/null +++ b/lib/didComponents.js @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ + +/** + * Using a colon (:) as the delimiter, split the identifier + * into its components: a scheme, a method, a version, and a + * multibaseValue. If there are only three components set the + * version to the string value 1 and use the last value as + * the multibaseValue. + * + * @param {object} options - Options to use. + * @param {string} options.did - A did as a string. + * + * @returns {object} The components of the did. +*/ +export const getDidComponents = ({did}) => { + const parts = did.split(':'); + // multibase maybe undefined + const [scheme, method, version, multibase] = parts; + return { + scheme, + method, + // if multibase exists use the version + version: multibase ? version : '1', + // if multibase exists use multibase + multibase: multibase || version, + parts, + did + }; +}; diff --git a/lib/didErrors.js b/lib/didErrors.js new file mode 100644 index 0000000..a0693ff --- /dev/null +++ b/lib/didErrors.js @@ -0,0 +1,21 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ + +//FIXME these errors should probably be in did-method-key + +export class InvalidDid extends Error { + constructor(message) { + super(message); + this.name = 'InvalidDidError'; + this.code = 'invalidDid'; + } +} + +export class InvalidDidUrl extends Error { + constructor(message) { + super(message); + this.name = 'InvalidDidUrlError'; + this.code = 'invalidDidUrl'; + } +} diff --git a/lib/didKeyErrors.js b/lib/didKeyErrors.js new file mode 100644 index 0000000..bc8e1ee --- /dev/null +++ b/lib/didKeyErrors.js @@ -0,0 +1,37 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ + +//FIXME these errors should probably be in did-method-key + +export class InvalidPublicKey extends Error { + constructor(message) { + super(message); + this.name = 'InvalidPublicKeyError'; + this.code = 'invalidPublicKey'; + } +} + +export class InvalidPublicKeyLength extends Error { + constructor(message) { + super(message); + this.name = 'InvalidPublicKeyLengthError'; + this.code = 'invalidPublicKeyLength'; + } +} + +export class InvalidPublicKeyType extends Error { + constructor(message) { + super(message); + this.name = 'InvalidPublicKeyTypeError'; + this.code = 'invalidPublicKeyType'; + } +} + +export class UnsupportedPublicKeyType extends Error { + constructor(message) { + super(message); + this.name = 'UnsupportedPublicKeyTypeError'; + this.code = 'unsupportedPublicKeyType'; + } +} diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..647b0a5 --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,3 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ diff --git a/lib/main.js b/lib/main.js index 7e65f16..021f0e7 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ import 'bedrock-express'; diff --git a/lib/validators.js b/lib/validators.js new file mode 100644 index 0000000..2a0bba2 --- /dev/null +++ b/lib/validators.js @@ -0,0 +1,77 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ + +import * as didErrors from './didErrors.js'; +import * as didKeyErrors from './didKeyErrors.js'; +import {getDidComponents} from './didComponents.js'; + +export const validateDid = did => { + const didComponents = getDidComponents({did}); + return _validateDidComponents({didComponents}); +}; + +/** + * Validates the components of a did. + * + * @param {object} options - Options to use. + * @param {object} options.didComponents - An object with + * various did components. + * + * @throws {Error} Throws did related errors. + * + * @returns {boolean} - Are the did components valid? + */ +// fixme this should probably be moved into the did-method-key +// get function and be run before returning a didDocument +function _validateDidComponents({didComponents}) { + const didKeyCompliant = _validateDidKey({didComponents}); + const ed25519Compliant = _validateMultibaseEd25519({didComponents}); + return didKeyCompliant && ed25519Compliant; +} + +/** + * General validation for did:keys indendenpent + * of key type specific validation. + * + * @param {object} options - Options to use. + * @param {object} options.didComponents - An object with + * did components such as schema and method. + * + * @throws {Error} Throws general did:key errors. + * + * @returns {boolean} If the didComponents are valid. + */ +function _validateDidKey({didComponents}) { + const {scheme, method, version, multibase = ''} = didComponents; + if(scheme !== 'did') { + throw new didErrors.InvalidDid(`Scheme must be "did" received "${scheme}"`); + } + if(method !== 'key') { + throw new didErrors.InvalidDid(`Method must be "key" received "${method}"`); + } + const validVersion = _validateVersion({version}); + if(!multibase.startsWith('z')) { + throw new didErrors.InvalidDid( + `Multibase must start with "z" received ${multibase[0]}`); + } + return validVersion; +} + +function _validateVersion({version}) { + try { + const versionNumber = Number.parse(version); + if(versionNumber <= 0) { + throw new didErrors.InvalidDid( + `Version must be a positive integer recieved "${versionNumber}"`); + } + } catch(e) { + throw new didErrors.InvalidDid( + `Version must be a number received "${version}"`); + } + return true; +} + +function _validateMultibaseEd25519({didComponents}) { + const {multibase} = didComponents; +} From e2b3a3261a2e6bf875ecaf6487fb045bd9cba38d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 29 Jul 2022 16:26:40 -0400 Subject: [PATCH 02/55] Add DidResolutionResult class and use it. --- lib/DidResolutionResult.js | 21 +++++++++++++++ lib/didComponents.js | 10 ++++++- lib/didResolver.js | 39 +++++++++++++++++++++++++++ lib/helpers.js | 3 --- lib/http.js | 55 +++++++++----------------------------- lib/validators.js | 10 ++----- 6 files changed, 83 insertions(+), 55 deletions(-) create mode 100644 lib/DidResolutionResult.js create mode 100644 lib/didResolver.js delete mode 100644 lib/helpers.js diff --git a/lib/DidResolutionResult.js b/lib/DidResolutionResult.js new file mode 100644 index 0000000..4518532 --- /dev/null +++ b/lib/DidResolutionResult.js @@ -0,0 +1,21 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ + +export class DidResolutionResult { + constructor({ + isDidUrl, + didDocument = {}, + didDocumentMetadata = null, + error + }) { + this['@context'] = 'https://w3id.org/did-resolution/v1'; + this.didDocument = didDocument; + this.didDocumentMetadata = didDocumentMetadata; + const metadataProperty = isDidUrl ? + 'didDereferencingMetadata' : 'didResolutionMetadata'; + this[metadataProperty] = { + error: error.code + }; + } +} diff --git a/lib/didComponents.js b/lib/didComponents.js index 18e0115..21e584b 100644 --- a/lib/didComponents.js +++ b/lib/didComponents.js @@ -16,6 +16,11 @@ */ export const getDidComponents = ({did}) => { const parts = did.split(':'); + //FIXME this could fail + const didUrl = new URL(did); + // if there is a fragment `#` or a service `?service` + // then we are dereferencing a did url + const isDidUrl = (didUrl.hash || didUrl.search); // multibase maybe undefined const [scheme, method, version, multibase] = parts; return { @@ -26,6 +31,9 @@ export const getDidComponents = ({did}) => { // if multibase exists use multibase multibase: multibase || version, parts, - did + did, + fragment: didUrl.hash, + parameters: didUrl.search, + isDidUrl }; }; diff --git a/lib/didResolver.js b/lib/didResolver.js new file mode 100644 index 0000000..c9a4843 --- /dev/null +++ b/lib/didResolver.js @@ -0,0 +1,39 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ +import {didIo} from 'bedrock-did-io'; +import {getDidComponents} from './didComponents.js'; +import {DidResolutionResult} from './ResolutionResult'; + +/** + * Resolves a did after validating it. + * + * @param {object} options - Options to use. + * @param {string} options.did - The did or didUrl being resolved. + * @param {Array} options.supportedMethods - A list of + * did methods this resolver supports. + * @param {object} options.didOptions - Options passed to the resolver. + * + * @returns {number, object} - Returns a status code and the resolution data. + */ +export const resolveDid = async ({did, supportedMethods, didOptions}) => { + const didComponents = getDidComponents({did}); + const {isDidUrl} = didComponents; + let error; + try { + + } catch(e) { + error = e; + } + if(error) { + return { + statusCode: error.statusCode || 500, + resolutionResult: new DidResolutionResult({error, isDidUrl}) + }; + } + const didDocument = await didIo.get({did}); + return { + statusCode: 200, + resolutionResult: new DidResolutionResult({didDocument, isDidUrl}) + }; +}; diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index 647b0a5..0000000 --- a/lib/helpers.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! - * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. - */ diff --git a/lib/http.js b/lib/http.js index 19f593d..757f0b2 100644 --- a/lib/http.js +++ b/lib/http.js @@ -3,8 +3,8 @@ */ import {asyncHandler} from 'bedrock-express'; import bedrock from 'bedrock'; -import {didIo} from 'bedrock-did-io'; import logger from './logger'; +import {resolveDid} from './didResolver.js'; const {config} = bedrock; bedrock.events.on('bedrock-express.configure.routes', app => { @@ -12,50 +12,19 @@ bedrock.events.on('bedrock-express.configure.routes', app => { app.get( routes.basePath, asyncHandler(async (req, res) => { - // this can be resolution or dereferencing meta data - const metaData = {}; - const resolutionResult = { - '@context': 'https://w3id.org/did-resolution/v1', - didDocument: {}, - didDocumentMetadata: null, - didResolutionMetadata: metaData - }; const {did} = req.params; - const parsedDid = new URL(did); - // if there is a fragment `#` or a service `?service` - // then we are dereferencing a did url - const didUrl = (parsedDid.hash || parsedDid.search); - if(didUrl) { - // add a did dereferencing meta - resolutionResult.didDereferencingMetadata = metaData; - // delete the resolution metadata - delete resolutionResult.didResolutionMetadata; - } - // the second value should always be the method - const [prefix, method, id] = did.split(':'); - // a did must have a did prefix, method, and id - if(!((prefix === 'did') && method && id)) { - metaData.error = didUrl ? 'invalidDidUrl' : 'invalidDid'; - return res.status(400).json(resolutionResult); - } - if(!supportedMethods.includes(method)) { - //FIXME this might not be the right error code - metaData.error = 'representationNotSupported'; - return res.status(406).json(resolutionResult); - } + // did options are passed to the resolver via url queries + // except some are headers on the request + const didOptions = {...req.headers, ...req.query}; try { - resolutionResult.didDocument = await didIo.get({did}); - } catch(e) { - //FIXME the did resolver error could contain invalidDid, notFound, - //or representationNotSupported we need to check for those errors - //and newer errors and add that information in the future - logger.error('DID Resolution error', {error: e}); - // the spec doesn't seem to handle what occurs if the - // did resolver fails for reasons unrelated to the did such - // as database timeouts. - metaData.error = 'InternalError'; - return res.status(500).json(resolutionResult); + const { + statusCode, + resolutionResult + } = await resolveDid({did, supportedMethods, didOptions}); + res.status(statusCode).json(resolutionResult); + } catch(error) { + logger.error(`Did resolution error ${did}`, {error}); + throw error; } - res.json(resolutionResult); })); }); diff --git a/lib/validators.js b/lib/validators.js index 2a0bba2..09bdf2a 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -4,12 +4,6 @@ import * as didErrors from './didErrors.js'; import * as didKeyErrors from './didKeyErrors.js'; -import {getDidComponents} from './didComponents.js'; - -export const validateDid = did => { - const didComponents = getDidComponents({did}); - return _validateDidComponents({didComponents}); -}; /** * Validates the components of a did. @@ -24,11 +18,11 @@ export const validateDid = did => { */ // fixme this should probably be moved into the did-method-key // get function and be run before returning a didDocument -function _validateDidComponents({didComponents}) { +export const validateDid = ({didComponents}) => { const didKeyCompliant = _validateDidKey({didComponents}); const ed25519Compliant = _validateMultibaseEd25519({didComponents}); return didKeyCompliant && ed25519Compliant; -} +}; /** * General validation for did:keys indendenpent From c28c251c80076fb53abd0d240acb5555655862ff Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 29 Jul 2022 16:32:21 -0400 Subject: [PATCH 03/55] Use more except didKeyComponents over didComponents. --- lib/didComponents.js | 4 ++-- lib/didResolver.js | 11 ++++++----- lib/validators.js | 20 ++++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/didComponents.js b/lib/didComponents.js index 21e584b..aca540f 100644 --- a/lib/didComponents.js +++ b/lib/didComponents.js @@ -14,13 +14,13 @@ * * @returns {object} The components of the did. */ -export const getDidComponents = ({did}) => { +export const getDidKeyComponents = ({did}) => { const parts = did.split(':'); //FIXME this could fail const didUrl = new URL(did); // if there is a fragment `#` or a service `?service` // then we are dereferencing a did url - const isDidUrl = (didUrl.hash || didUrl.search); + const isDidUrl = didUrl.hash || didUrl.search; // multibase maybe undefined const [scheme, method, version, multibase] = parts; return { diff --git a/lib/didResolver.js b/lib/didResolver.js index c9a4843..e15cfba 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -2,8 +2,9 @@ * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ import {didIo} from 'bedrock-did-io'; -import {getDidComponents} from './didComponents.js'; -import {DidResolutionResult} from './ResolutionResult'; +import {getDidKeyComponents} from './didComponents.js'; +import {DidResolutionResult} from './DidResolutionResult.js'; +import {validateDidKey} from './validators.js'; /** * Resolves a did after validating it. @@ -17,11 +18,11 @@ import {DidResolutionResult} from './ResolutionResult'; * @returns {number, object} - Returns a status code and the resolution data. */ export const resolveDid = async ({did, supportedMethods, didOptions}) => { - const didComponents = getDidComponents({did}); - const {isDidUrl} = didComponents; + const didKeyComponents = getDidKeyComponents({did}); + const {isDidUrl} = didKeyComponents; let error; try { - + await validateDidKey({didKeyComponents}); } catch(e) { error = e; } diff --git a/lib/validators.js b/lib/validators.js index 09bdf2a..77afcfc 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -9,7 +9,7 @@ import * as didKeyErrors from './didKeyErrors.js'; * Validates the components of a did. * * @param {object} options - Options to use. - * @param {object} options.didComponents - An object with + * @param {object} options.didKeyComponents - An object with * various did components. * * @throws {Error} Throws did related errors. @@ -18,9 +18,9 @@ import * as didKeyErrors from './didKeyErrors.js'; */ // fixme this should probably be moved into the did-method-key // get function and be run before returning a didDocument -export const validateDid = ({didComponents}) => { - const didKeyCompliant = _validateDidKey({didComponents}); - const ed25519Compliant = _validateMultibaseEd25519({didComponents}); +export const validateDidKey = ({didKeyComponents}) => { + const didKeyCompliant = _validateDidKey({didKeyComponents}); + const ed25519Compliant = _validateMultibaseEd25519({didKeyComponents}); return didKeyCompliant && ed25519Compliant; }; @@ -29,15 +29,15 @@ export const validateDid = ({didComponents}) => { * of key type specific validation. * * @param {object} options - Options to use. - * @param {object} options.didComponents - An object with + * @param {object} options.didKeyComponents - An object with * did components such as schema and method. * * @throws {Error} Throws general did:key errors. * - * @returns {boolean} If the didComponents are valid. + * @returns {boolean} If the didKeyComponents are valid. */ -function _validateDidKey({didComponents}) { - const {scheme, method, version, multibase = ''} = didComponents; +function _validateDidKey({didKeyComponents}) { + const {scheme, method, version, multibase = ''} = didKeyComponents; if(scheme !== 'did') { throw new didErrors.InvalidDid(`Scheme must be "did" received "${scheme}"`); } @@ -66,6 +66,6 @@ function _validateVersion({version}) { return true; } -function _validateMultibaseEd25519({didComponents}) { - const {multibase} = didComponents; +function _validateMultibaseEd25519({didKeyComponents}) { + const {multibase} = didKeyComponents; } From 38fde217b498a54cb3a92ff26ac17514c1c43108 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 29 Jul 2022 16:43:50 -0400 Subject: [PATCH 04/55] Correct mistake Number.parseInt not Number.parse. --- lib/validators.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validators.js b/lib/validators.js index 77afcfc..f4f6e6b 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -54,7 +54,7 @@ function _validateDidKey({didKeyComponents}) { function _validateVersion({version}) { try { - const versionNumber = Number.parse(version); + const versionNumber = Number.parseInt(version); if(versionNumber <= 0) { throw new didErrors.InvalidDid( `Version must be a positive integer recieved "${versionNumber}"`); From 1299da9275f41b43f490e6692651eb923b452e74 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 29 Jul 2022 16:44:24 -0400 Subject: [PATCH 05/55] Update lint stuff. --- .eslintrc.cjs | 14 ++++++++++++++ .eslintrc.js | 7 ------- package.json | 6 ++++-- 3 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 .eslintrc.cjs delete mode 100644 .eslintrc.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..c5e11a9 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,14 @@ +module.exports = { + env: { + node: true + }, + extends: [ + 'eslint-config-digitalbazaar', + 'eslint-config-digitalbazaar/jsdoc', + 'eslint-config-digitalbazaar/module', + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + } +}; diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2297f74..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - env: { - node: true - }, - extends: 'digitalbazaar' -}; diff --git a/package.json b/package.json index 257276e..d7d891d 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,10 @@ "lib": "./lib" }, "devDependencies": { - "eslint": "^7.14.0", - "eslint-config-digitalbazaar": "^2.6.1" + "eslint": "^8.20.0", + "eslint-config-digitalbazaar": "^4.1.0", + "eslint-plugin-jsdoc": "^39.3.4", + "eslint-plugin-unicorn": "^43.0.2" }, "engines": { "node": ">=12" From 34fc90dd33ed843f00d112eef9d5c2cac068ce30 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 29 Jul 2022 17:01:03 -0400 Subject: [PATCH 06/55] Up engine to 16 & improve error reporting code. --- lib/DidResolutionResult.js | 8 +++++--- package.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/DidResolutionResult.js b/lib/DidResolutionResult.js index 4518532..8db4c18 100644 --- a/lib/DidResolutionResult.js +++ b/lib/DidResolutionResult.js @@ -14,8 +14,10 @@ export class DidResolutionResult { this.didDocumentMetadata = didDocumentMetadata; const metadataProperty = isDidUrl ? 'didDereferencingMetadata' : 'didResolutionMetadata'; - this[metadataProperty] = { - error: error.code - }; + this[metadataProperty] = {}; + // only define error if it was passed in + if(error) { + this[metadataProperty].error = error.code || 'internalError'; + } } } diff --git a/package.json b/package.json index d7d891d..affa6af 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,6 @@ "eslint-plugin-unicorn": "^43.0.2" }, "engines": { - "node": ">=12" + "node": ">=16" } } From a17e0ea99798d43438bd2be336898d7019656cea Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sat, 30 Jul 2022 08:04:38 -0400 Subject: [PATCH 07/55] Remove use strict & correct invalid jsdoc param. --- lib/didResolver.js | 2 +- lib/index.js | 1 - lib/logger.js | 1 - test/test.config.js | 1 - test/test.js | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index e15cfba..7815c94 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -15,7 +15,7 @@ import {validateDidKey} from './validators.js'; * did methods this resolver supports. * @param {object} options.didOptions - Options passed to the resolver. * - * @returns {number, object} - Returns a status code and the resolution data. + * @returns {object} - Returns a status code and the resolution data. */ export const resolveDid = async ({did, supportedMethods, didOptions}) => { const didKeyComponents = getDidKeyComponents({did}); diff --git a/lib/index.js b/lib/index.js index e731f70..0ce952b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,6 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -'use strict'; // translate `main.js` to CommonJS require = require('esm')(module); diff --git a/lib/logger.js b/lib/logger.js index 73c0f40..dde5aae 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -2,7 +2,6 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -'use strict'; import bedrock from 'bedrock'; diff --git a/test/test.config.js b/test/test.config.js index b5bb5cc..a3a1a39 100644 --- a/test/test.config.js +++ b/test/test.config.js @@ -1,7 +1,6 @@ /* * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -'use strict'; const {config} = require('bedrock'); const path = require('path'); diff --git a/test/test.js b/test/test.js index 83a89e1..4a2bee4 100644 --- a/test/test.js +++ b/test/test.js @@ -1,7 +1,6 @@ /* * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -'use strict'; const bedrock = require('bedrock'); require('bedrock-https-agent'); From e26070bbfa1b54b955bff751f015242101d736a1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 1 Aug 2022 13:46:57 -0400 Subject: [PATCH 08/55] Use bnid for public key length errors. --- lib/DidResolutionResult.js | 14 +++++++++++++- lib/didResolver.js | 15 ++++++++------- lib/validators.js | 17 +++++++++++++++-- package.json | 1 + 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/DidResolutionResult.js b/lib/DidResolutionResult.js index 8db4c18..79bce8e 100644 --- a/lib/DidResolutionResult.js +++ b/lib/DidResolutionResult.js @@ -2,9 +2,21 @@ * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ +/** + * Creates the json-ld for the response. + * + * @param {object} options - Options to use. + * @param {boolean} [options.isDidUrl=false] - Does the response need + * didDereferencingMetadata? + * @param {object} [options.didDocument={}] - The didDocument for the + * response. + * @param {object|null} [options.didDocumentMetadata=null] - Meta data + * for the didDocument. + * @param {Error} options.error - An error if any was raised. + */ export class DidResolutionResult { constructor({ - isDidUrl, + isDidUrl = false, didDocument = {}, didDocumentMetadata = null, error diff --git a/lib/didResolver.js b/lib/didResolver.js index 7815c94..0edf1ed 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -18,11 +18,17 @@ import {validateDidKey} from './validators.js'; * @returns {object} - Returns a status code and the resolution data. */ export const resolveDid = async ({did, supportedMethods, didOptions}) => { - const didKeyComponents = getDidKeyComponents({did}); - const {isDidUrl} = didKeyComponents; + let isDidUrl = false; let error; try { + const didKeyComponents = getDidKeyComponents({did}); + isDidUrl = didKeyComponents.isDidUrl; await validateDidKey({didKeyComponents}); + const didDocument = await didIo.get({did}); + return { + statusCode: 200, + resolutionResult: new DidResolutionResult({didDocument, isDidUrl}) + }; } catch(e) { error = e; } @@ -32,9 +38,4 @@ export const resolveDid = async ({did, supportedMethods, didOptions}) => { resolutionResult: new DidResolutionResult({error, isDidUrl}) }; } - const didDocument = await didIo.get({did}); - return { - statusCode: 200, - resolutionResult: new DidResolutionResult({didDocument, isDidUrl}) - }; }; diff --git a/lib/validators.js b/lib/validators.js index f4f6e6b..f7b0a16 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -4,6 +4,10 @@ import * as didErrors from './didErrors.js'; import * as didKeyErrors from './didKeyErrors.js'; +import {IdDecoder} from 'bnid'; + + +const idDecoder = new IdDecoder(); /** * Validates the components of a did. @@ -25,7 +29,7 @@ export const validateDidKey = ({didKeyComponents}) => { }; /** - * General validation for did:keys indendenpent + * General validation for did:keys independent * of key type specific validation. * * @param {object} options - Options to use. @@ -57,7 +61,7 @@ function _validateVersion({version}) { const versionNumber = Number.parseInt(version); if(versionNumber <= 0) { throw new didErrors.InvalidDid( - `Version must be a positive integer recieved "${versionNumber}"`); + `Version must be a positive integer received "${versionNumber}"`); } } catch(e) { throw new didErrors.InvalidDid( @@ -66,6 +70,15 @@ function _validateVersion({version}) { return true; } +// FIXME we should probably refactor +// ed25519-verification-key-2020/lib/Ed25519VerificationKey2020.js +// to use these newer errors function _validateMultibaseEd25519({didKeyComponents}) { const {multibase} = didKeyComponents; + const publicKeyBytes = idDecoder.decode(multibase); +console.log({multibase, publicKeyBytes}); + if(publicKeyBytes.length !== 32) { + throw new didKeyErrors.InvalidPublicKeyLength( + 'Expected 32 byte publicKey.'); + } } diff --git a/package.json b/package.json index affa6af..b159c4a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "homepage": "https://github.com/digitalbazaar/bedrock-did-resolver-http", "dependencies": { "bedrock-did-io": "^5.0.0", + "bnid": "^3.0.0", "esm": "^3.2.25" }, "peerDependencies": { From d0b1f22117ed3f86b35ca0a566d2b3066bcc680a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 1 Aug 2022 13:58:19 -0400 Subject: [PATCH 09/55] Use @bedrock/did-io latest. --- lib/didResolver.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 0edf1ed..5ddec77 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -1,7 +1,7 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import {didIo} from 'bedrock-did-io'; +import {didIo} from '@bedrock/did-io'; import {getDidKeyComponents} from './didComponents.js'; import {DidResolutionResult} from './DidResolutionResult.js'; import {validateDidKey} from './validators.js'; diff --git a/package.json b/package.json index b159c4a..3205219 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/digitalbazaar/bedrock-did-resolver-http", "dependencies": { - "bedrock-did-io": "^5.0.0", + "@bedrock/did-io": "^9.0.1", "bnid": "^3.0.0", "esm": "^3.2.25" }, From 29b57ec3556c0c1c1c152a014cbfea10e7b8d418 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 1 Aug 2022 14:19:38 -0400 Subject: [PATCH 10/55] Convert to es6 module. --- lib/config.js | 3 +-- lib/didResolver.js | 2 +- lib/http.js | 7 ++++--- lib/index.js | 8 ++++---- lib/logger.js | 2 +- lib/main.js | 7 ------- package.json | 10 +++++----- test/package.json | 13 +++++++------ test/test.config.js | 7 +++++-- test/test.js | 12 ++++++------ 10 files changed, 34 insertions(+), 37 deletions(-) delete mode 100644 lib/main.js diff --git a/lib/config.js b/lib/config.js index 21a7294..012cfbf 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,8 +1,7 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -import bedrock from 'bedrock'; -const {config} = bedrock; +import {config} from '@bedrock/core'; const namespace = 'bedrock-did-resolver-http'; const cfg = config[namespace] = {}; diff --git a/lib/didResolver.js b/lib/didResolver.js index 5ddec77..8cf1868 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -2,8 +2,8 @@ * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ import {didIo} from '@bedrock/did-io'; -import {getDidKeyComponents} from './didComponents.js'; import {DidResolutionResult} from './DidResolutionResult.js'; +import {getDidKeyComponents} from './didComponents.js'; import {validateDidKey} from './validators.js'; /** diff --git a/lib/http.js b/lib/http.js index 757f0b2..a0d5c16 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1,9 +1,10 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import {asyncHandler} from 'bedrock-express'; -import bedrock from 'bedrock'; -import logger from './logger'; + +import * as bedrock from '@bedrock/core'; +import {asyncHandler} from '@bedrock/express'; +import logger from './logger.js'; import {resolveDid} from './didResolver.js'; const {config} = bedrock; diff --git a/lib/index.js b/lib/index.js index 0ce952b..12ce93d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,7 @@ /*! - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ +import '@bedrock/express'; -// translate `main.js` to CommonJS -require = require('esm')(module); -module.exports = require('./main.js'); +import './config.js'; +import './http.js'; diff --git a/lib/logger.js b/lib/logger.js index dde5aae..e921632 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -3,7 +3,7 @@ * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import bedrock from 'bedrock'; +import * as bedrock from '@bedrock/core'; const logger = bedrock.loggers.get('app').child('bedrock-did-resolver-http'); diff --git a/lib/main.js b/lib/main.js deleted file mode 100644 index 021f0e7..0000000 --- a/lib/main.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. - */ -import 'bedrock-express'; - -import './config.js'; -import './http.js'; diff --git a/package.json b/package.json index 3205219..f214ecf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "bedrock-did-resolver-http", "version": "0.0.1-0", "description": "Bedrock HTTP API", - "main": "./lib", + "type": "module", + "main": "./lib/index.js", "scripts": { "lint": "eslint ." }, @@ -24,12 +25,11 @@ "homepage": "https://github.com/digitalbazaar/bedrock-did-resolver-http", "dependencies": { "@bedrock/did-io": "^9.0.1", - "bnid": "^3.0.0", - "esm": "^3.2.25" + "bnid": "^3.0.0" }, "peerDependencies": { - "bedrock": "^4.4.3", - "bedrock-express": "^6.3.0" + "@bedrock/core": "^6.0.0", + "@bedrock/express": "^8.0.0" }, "directories": { "lib": "./lib" diff --git a/test/package.json b/test/package.json index 735c2f9..c4f3c83 100644 --- a/test/package.json +++ b/test/package.json @@ -2,6 +2,7 @@ "name": "bedrock-did-resolver-http-test", "version": "0.0.1-0", "private": true, + "type": "module", "scripts": { "test": "node --preserve-symlinks test.js test", "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text-summary npm test", @@ -9,13 +10,13 @@ "coverage-report": "nyc report" }, "dependencies": { - "bedrock": "^4.1.1", - "bedrock-express": "^3.2.0", - "bedrock-https-agent": "^2.0.0", + "@bedrock/core": "^6.0.0", "bedrock-did-resolver-http": "file:..", - "bedrock-mongodb": "^8.2.0", - "bedrock-server": "^2.7.0", - "bedrock-test": "^5.3.2", + "@bedrock/express": "^8.0.0", + "@bedrock/https-agent": "^4.0.0", + "@bedrock/mongodb": "^10.0.0", + "@bedrock/server": "^5.0.0", + "@bedrock/test": "^8.0.0", "cross-env": "^7.0.3", "nyc": "^15.1.0" }, diff --git a/test/test.config.js b/test/test.config.js index a3a1a39..30e4900 100644 --- a/test/test.config.js +++ b/test/test.config.js @@ -2,8 +2,11 @@ * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -const {config} = require('bedrock'); -const path = require('path'); +import {config} from '@bedrock/core'; +import {fileURLToPath} from 'node:url'; +import path from 'path'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); // MongoDB config.mongodb.name = 'bedrock_did_resolver_http_test'; diff --git a/test/test.js b/test/test.js index 4a2bee4..065b8aa 100644 --- a/test/test.js +++ b/test/test.js @@ -2,10 +2,10 @@ * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -const bedrock = require('bedrock'); -require('bedrock-https-agent'); -require('bedrock-mongodb'); -require('bedrock-did-resolver-http'); +import * as bedrock from '@bedrock/core'; +import '@bedrock/https-agent'; +import '@bedrock/mongodb'; +import 'bedrock-did-resolver-http'; +import '@bedrock/test'; -require('bedrock-test'); -bedrock.start(); +bedrock.start().catch(err => console.error(err)); From 992b590987f548b578cbb2303b73aa38d0b5e3de Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 1 Aug 2022 18:02:59 -0400 Subject: [PATCH 11/55] Add MULTICODEC ed25519 header and get bytes. --- lib/validators.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/validators.js b/lib/validators.js index f7b0a16..8b5d46a 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -6,8 +6,9 @@ import * as didErrors from './didErrors.js'; import * as didKeyErrors from './didKeyErrors.js'; import {IdDecoder} from 'bnid'; - const idDecoder = new IdDecoder(); +// multicodec ed25519-pub header as varint +const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); /** * Validates the components of a did. @@ -75,8 +76,8 @@ function _validateVersion({version}) { // to use these newer errors function _validateMultibaseEd25519({didKeyComponents}) { const {multibase} = didKeyComponents; - const publicKeyBytes = idDecoder.decode(multibase); -console.log({multibase, publicKeyBytes}); + const publicKeyBytes = idDecoder.decode(multibase). + slice(MULTICODEC_ED25519_PUB_HEADER.length); if(publicKeyBytes.length !== 32) { throw new didKeyErrors.InvalidPublicKeyLength( 'Expected 32 byte publicKey.'); From 6dc9f642b0578ba0f0220f48cfab2a5defabcc1c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 2 Aug 2022 12:22:47 -0400 Subject: [PATCH 12/55] Add check for invalidDidUrl. --- lib/didComponents.js | 9 +++++--- lib/didResolver.js | 2 ++ lib/validators.js | 51 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/lib/didComponents.js b/lib/didComponents.js index aca540f..39fbb23 100644 --- a/lib/didComponents.js +++ b/lib/didComponents.js @@ -16,11 +16,10 @@ */ export const getDidKeyComponents = ({did}) => { const parts = did.split(':'); - //FIXME this could fail const didUrl = new URL(did); // if there is a fragment `#` or a service `?service` // then we are dereferencing a did url - const isDidUrl = didUrl.hash || didUrl.search; + const isDidUrl = Boolean(didUrl.hash.length || didUrl.search.length); // multibase maybe undefined const [scheme, method, version, multibase] = parts; return { @@ -32,8 +31,12 @@ export const getDidKeyComponents = ({did}) => { multibase: multibase || version, parts, did, + // contains the fragment to dereference fragment: didUrl.hash, - parameters: didUrl.search, + // contains the search string + search: didUrl.search, + // contains a map with key value pairs + searchParams: didUrl.searchParams, isDidUrl }; }; diff --git a/lib/didResolver.js b/lib/didResolver.js index 8cf1868..311c935 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -4,6 +4,7 @@ import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import {getDidKeyComponents} from './didComponents.js'; +import logger from './logger.js'; import {validateDidKey} from './validators.js'; /** @@ -30,6 +31,7 @@ export const resolveDid = async ({did, supportedMethods, didOptions}) => { resolutionResult: new DidResolutionResult({didDocument, isDidUrl}) }; } catch(e) { + logger.error(`did resolution error: ${did}`, {error: e}); error = e; } if(error) { diff --git a/lib/validators.js b/lib/validators.js index 8b5d46a..e406e6f 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -42,13 +42,18 @@ export const validateDidKey = ({didKeyComponents}) => { * @returns {boolean} If the didKeyComponents are valid. */ function _validateDidKey({didKeyComponents}) { - const {scheme, method, version, multibase = ''} = didKeyComponents; + const {scheme, method, version, multibase = '', did} = didKeyComponents; if(scheme !== 'did') { throw new didErrors.InvalidDid(`Scheme must be "did" received "${scheme}"`); } if(method !== 'key') { throw new didErrors.InvalidDid(`Method must be "key" received "${method}"`); } + const invalidDidUrl = (didKeyComponents.isDidUrl && + !_isValidDidUrl({didUrl: did})); + if(invalidDidUrl) { + throw new didErrors.InvalidDidUrl(`Invalid didUrl ${did}`); + } const validVersion = _validateVersion({version}); if(!multibase.startsWith('z')) { throw new didErrors.InvalidDid( @@ -83,3 +88,47 @@ function _validateMultibaseEd25519({didKeyComponents}) { 'Expected 32 byte publicKey.'); } } + +/** + * This function comes from the did-test-suite. + * + * @see https://github.com/w3c/did-test-suite/ + * + * @param {object} options - Options to use. + * @param {string} options.did - A prospective did. + * + * @returns {boolean} - Returns true or false. +*/ +function _isValidDid({did}) { + const didRegex1 = new RegExp('^did:(?[a-z0-9]+):' + + '(?([a-zA-Z0-9\.\-_]|%[0-9a-fA-F]{2}|:)+$)'); + const didRegex2 = /:$/; + return didRegex1.test(did) && !didRegex2.test(did); +} + +/** + * This function comes from the did-test-suite. + * + * @see https://github.com/w3c/did-test-suite/ + * + * @param {object} options - Options to use. + * @param {string} options.didUrl - A prospective didUrl. + * + * @returns {boolean} - Returns true or false. +*/ +function _isValidDidUrl({didUrl}) { + const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; + const didUrlPattern = + '^' + + 'did:' + + '([a-z0-9]+)' + // method_name + '(:' + // method-specific-id + '([a-zA-Z0-9\\.\\-_]|%[0-9a-fA-F]{2})+' + + ')+' + + '((/(' + pchar + ')+)+)?' + // path-abempty + '(\\?(' + pchar + '|/|\\?)+)?' + // [ "?" query ] + '(#(' + pchar + '|/|\\?)+)?' + // [ "#" fragment ] + '$' + ; + return new RegExp(didUrlPattern).test(didUrl); +} From 510f909e1d28e90bb63f453e39813d5f5aad0410 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 2 Aug 2022 14:57:36 -0400 Subject: [PATCH 13/55] Add better tests for isDiDUrl & component split. --- lib/didComponents.js | 9 +++++---- lib/validators.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/didComponents.js b/lib/didComponents.js index 39fbb23..e386ce2 100644 --- a/lib/didComponents.js +++ b/lib/didComponents.js @@ -15,18 +15,19 @@ * @returns {object} The components of the did. */ export const getDidKeyComponents = ({did}) => { - const parts = did.split(':'); const didUrl = new URL(did); - // if there is a fragment `#` or a service `?service` + // if there is a fragment `#` or a service `?service` or it contains / // then we are dereferencing a did url - const isDidUrl = Boolean(didUrl.hash.length || didUrl.search.length); - // multibase maybe undefined + const isDidUrl = /[\/#?]/.test(did); + // split on : unless : is the last character in a did url /(?!:$):/ + const parts = did.split(/(?!:$):/); const [scheme, method, version, multibase] = parts; return { scheme, method, // if multibase exists use the version version: multibase ? version : '1', + // multibase maybe undefined // if multibase exists use multibase multibase: multibase || version, parts, diff --git a/lib/validators.js b/lib/validators.js index e406e6f..b54f3a9 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -101,7 +101,7 @@ function _validateMultibaseEd25519({didKeyComponents}) { */ function _isValidDid({did}) { const didRegex1 = new RegExp('^did:(?[a-z0-9]+):' + - '(?([a-zA-Z0-9\.\-_]|%[0-9a-fA-F]{2}|:)+$)'); + '(?([a-zA-Z0-9\\.\\-_]|%[0-9a-fA-F]{2}|:)+$)'); const didRegex2 = /:$/; return didRegex1.test(did) && !didRegex2.test(did); } From 963ab5674a8392ae2a1dd8b942c516cbaa9052f8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 2 Aug 2022 15:44:35 -0400 Subject: [PATCH 14/55] Get identifier & isDidUrl. --- lib/didComponents.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/didComponents.js b/lib/didComponents.js index e386ce2..6221483 100644 --- a/lib/didComponents.js +++ b/lib/didComponents.js @@ -16,11 +16,9 @@ */ export const getDidKeyComponents = ({did}) => { const didUrl = new URL(did); - // if there is a fragment `#` or a service `?service` or it contains / - // then we are dereferencing a did url - const isDidUrl = /[\/#?]/.test(did); + const {isDidUrl, identifier} = _getIdentifier({did}); // split on : unless : is the last character in a did url /(?!:$):/ - const parts = did.split(/(?!:$):/); + const parts = identifier.split(/(?!:$):/); const [scheme, method, version, multibase] = parts; return { scheme, @@ -41,3 +39,22 @@ export const getDidKeyComponents = ({did}) => { isDidUrl }; }; + +function _getIdentifier({did}) { + // if there is a fragment `#` or a service `?service` or it contains / + // then we are dereferencing a did url + const urlParts = /[\/#?]/; + const urlMatch = urlParts.exec(did); + // if there is a match this is a did url + if(urlMatch) { + return { + isDidUrl: true, + // the identifier should be the did before the url syntax starts + identifier: did.substring(0, urlMatch.index) + }; + } + return { + isDidUrl: false, + identifier: did + }; +} From 6248cb6effff069333d92937a8f83c6b66c30a4b Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 2 Aug 2022 16:57:42 -0400 Subject: [PATCH 15/55] Better separation of did url and identifier logic. --- lib/didComponents.js | 41 ++++++++++++++++++++++++++++++----------- lib/validators.js | 3 +-- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/didComponents.js b/lib/didComponents.js index 6221483..a0efc5c 100644 --- a/lib/didComponents.js +++ b/lib/didComponents.js @@ -15,8 +15,15 @@ * @returns {object} The components of the did. */ export const getDidKeyComponents = ({did}) => { - const didUrl = new URL(did); - const {isDidUrl, identifier} = _getIdentifier({did}); + const { + isDidUrl, + didEndIndex, + fragment, + search, + searchParams + } = _getUrlComponents({did}); + // the identifier should be the did before the url syntax starts + const identifier = isDidUrl ? did.substring(0, didEndIndex) : did; // split on : unless : is the last character in a did url /(?!:$):/ const parts = identifier.split(/(?!:$):/); const [scheme, method, version, multibase] = parts; @@ -30,31 +37,43 @@ export const getDidKeyComponents = ({did}) => { multibase: multibase || version, parts, did, + identifier, // contains the fragment to dereference - fragment: didUrl.hash, + fragment, // contains the search string - search: didUrl.search, + search, // contains a map with key value pairs - searchParams: didUrl.searchParams, + searchParams, isDidUrl }; }; -function _getIdentifier({did}) { +/** + * If the did is a did url it gets the fragment and searchParams. + * + * @private + * + * @param {object} options - Options to use. + * @param {string} options.did - A potential did url. + * + * @returns {object} The resulting url components. + */ +function _getUrlComponents({did}) { // if there is a fragment `#` or a service `?service` or it contains / // then we are dereferencing a did url const urlParts = /[\/#?]/; const urlMatch = urlParts.exec(did); - // if there is a match this is a did url if(urlMatch) { + const urlParts = new URL(did); return { isDidUrl: true, - // the identifier should be the did before the url syntax starts - identifier: did.substring(0, urlMatch.index) + fragment: urlParts.hash, + search: urlParts.search, + searchParams: urlParts.searchParams, + didEndIndex: urlMatch.index }; } return { - isDidUrl: false, - identifier: did + isDidUrl: false }; } diff --git a/lib/validators.js b/lib/validators.js index b54f3a9..9bc12d3 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -54,12 +54,11 @@ function _validateDidKey({didKeyComponents}) { if(invalidDidUrl) { throw new didErrors.InvalidDidUrl(`Invalid didUrl ${did}`); } - const validVersion = _validateVersion({version}); if(!multibase.startsWith('z')) { throw new didErrors.InvalidDid( `Multibase must start with "z" received ${multibase[0]}`); } - return validVersion; + return _validateVersion({version}); } function _validateVersion({version}) { From bea2e99f448b3b59c78838c5f98614dbb1215a0d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 09:37:10 -0400 Subject: [PATCH 16/55] Separate validators better. --- lib/config.js | 5 +- lib/didResolver.js | 4 +- lib/{ => validators}/didErrors.js | 0 lib/{ => validators}/didKeyErrors.js | 0 lib/{validators.js => validators/index.js} | 66 +++++++++++++++------- 5 files changed, 52 insertions(+), 23 deletions(-) rename lib/{ => validators}/didErrors.js (100%) rename lib/{ => validators}/didKeyErrors.js (100%) rename lib/{validators.js => validators/index.js} (71%) diff --git a/lib/config.js b/lib/config.js index 012cfbf..af6425b 100644 --- a/lib/config.js +++ b/lib/config.js @@ -6,8 +6,9 @@ import {config} from '@bedrock/core'; const namespace = 'bedrock-did-resolver-http'; const cfg = config[namespace] = {}; -// support did:key and veres one keys by default -cfg.supportedMethods = ['key', 'v1']; +// support did:key by default +// FIXME this can be expanded v1 aka veres-one at a later time +cfg.supportedMethods = ['key']; const basePath = '/1.0/resolve/identifiers/:did'; cfg.routes = {basePath}; diff --git a/lib/didResolver.js b/lib/didResolver.js index 311c935..da3fbbd 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -5,7 +5,7 @@ import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import {getDidKeyComponents} from './didComponents.js'; import logger from './logger.js'; -import {validateDidKey} from './validators.js'; +import {validateDidRequest} from './validators/index.js'; /** * Resolves a did after validating it. @@ -24,7 +24,7 @@ export const resolveDid = async ({did, supportedMethods, didOptions}) => { try { const didKeyComponents = getDidKeyComponents({did}); isDidUrl = didKeyComponents.isDidUrl; - await validateDidKey({didKeyComponents}); + await validateDidRequest({didKeyComponents, supportedMethods, didOptions}); const didDocument = await didIo.get({did}); return { statusCode: 200, diff --git a/lib/didErrors.js b/lib/validators/didErrors.js similarity index 100% rename from lib/didErrors.js rename to lib/validators/didErrors.js diff --git a/lib/didKeyErrors.js b/lib/validators/didKeyErrors.js similarity index 100% rename from lib/didKeyErrors.js rename to lib/validators/didKeyErrors.js diff --git a/lib/validators.js b/lib/validators/index.js similarity index 71% rename from lib/validators.js rename to lib/validators/index.js index 9bc12d3..e885768 100644 --- a/lib/validators.js +++ b/lib/validators/index.js @@ -7,7 +7,7 @@ import * as didKeyErrors from './didKeyErrors.js'; import {IdDecoder} from 'bnid'; const idDecoder = new IdDecoder(); -// multicodec ed25519-pub header as varint +// multicodec ed25519-pub header as Uint8Array const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); /** @@ -16,6 +16,10 @@ const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); * @param {object} options - Options to use. * @param {object} options.didKeyComponents - An object with * various did components. + * @param {Array} options.supportedMethods - A list of + * did:key methods this implementation supports. + * @param {object} options.didOptions - Options passed + * in the request. * * @throws {Error} Throws did related errors. * @@ -23,42 +27,66 @@ const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); */ // fixme this should probably be moved into the did-method-key // get function and be run before returning a didDocument -export const validateDidKey = ({didKeyComponents}) => { - const didKeyCompliant = _validateDidKey({didKeyComponents}); - const ed25519Compliant = _validateMultibaseEd25519({didKeyComponents}); - return didKeyCompliant && ed25519Compliant; +export const validateDidRequest = ({ + didKeyComponents, + supportedMethods, + didOptions +}) => { + const {scheme, method, version, multibase, did, isDidUrl} = didKeyComponents; + _validateDid({scheme, did, isDidUrl}); + if((isDidUrl && !_isValidDidUrl({didUrl: did}))) { + throw new didErrors.InvalidDidUrl(`Invalid didUrl ${did}`); + } + _validateRequestOptions({supportedMethods, didOptions, didKeyComponents}); + _validateDidKey({method, version, multibase}); + _validateMultibaseEd25519({didKeyComponents}); }; +function _validateRequestOptions({ + supportedMethods, + didOptions, +}) { + +} + /** * General validation for did:keys independent * of key type specific validation. * * @param {object} options - Options to use. - * @param {object} options.didKeyComponents - An object with - * did components such as schema and method. + * @param {string} options.method - A did:method. + * @param {string|number} options.version - A did:method:version. + * @param {string} [options.multibase = ''] - The multibase value + * of the did:key. * * @throws {Error} Throws general did:key errors. * - * @returns {boolean} If the didKeyComponents are valid. + * @returns {undefined} If the didKeyComponents are valid. */ -function _validateDidKey({didKeyComponents}) { - const {scheme, method, version, multibase = '', did} = didKeyComponents; - if(scheme !== 'did') { - throw new didErrors.InvalidDid(`Scheme must be "did" received "${scheme}"`); - } +function _validateDidKey({ + method, + version, + multibase = '', +}) { if(method !== 'key') { throw new didErrors.InvalidDid(`Method must be "key" received "${method}"`); } - const invalidDidUrl = (didKeyComponents.isDidUrl && - !_isValidDidUrl({didUrl: did})); - if(invalidDidUrl) { - throw new didErrors.InvalidDidUrl(`Invalid didUrl ${did}`); - } if(!multibase.startsWith('z')) { throw new didErrors.InvalidDid( `Multibase must start with "z" received ${multibase[0]}`); } - return _validateVersion({version}); + _validateVersion({version}); +} + +function _validateDid({scheme, did, isDidUrl}) { + // this is more specific than just invalidDid + if(scheme !== 'did') { + throw new didErrors.InvalidDid(`Scheme must be "did" received "${scheme}"`); + } + // this will catch invalid dids such as did:key:@@ + if(!isDidUrl && !_isValidDid({did})) { + throw new didErrors.InvalidDid(`Invalid did ${did}`); + } } function _validateVersion({version}) { From 0a2e89f455f7e20dae4b22b48443c49e203a4747 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 14:19:51 -0400 Subject: [PATCH 17/55] Implement 100% success with latest did:key spec. --- lib/config.js | 13 +++- lib/didResolver.js | 22 +++++- lib/http.js | 16 ++-- lib/validators/didResolverErrors.js | 28 +++++++ lib/validators/index.js | 111 +++++++++++++++++++++++++--- 5 files changed, 170 insertions(+), 20 deletions(-) create mode 100644 lib/validators/didResolverErrors.js diff --git a/lib/config.js b/lib/config.js index af6425b..78c5348 100644 --- a/lib/config.js +++ b/lib/config.js @@ -6,9 +6,16 @@ import {config} from '@bedrock/core'; const namespace = 'bedrock-did-resolver-http'; const cfg = config[namespace] = {}; -// support did:key by default -// FIXME this can be expanded v1 aka veres-one at a later time -cfg.supportedMethods = ['key']; +// a list of which did:methods we supported +// experimental is for tests only +cfg.supportedMethods = ['key', 'v1', 'experimental']; +// FIXME this can be expanded to support Multikey or JsonWebKey2020 +cfg.publicKeyFormats = [ + 'Ed25519VerificationKey2020', + 'X25519KeyAgreementKey2020', + // this is strictly for testing + 'ExperimentalVerificationKey2022' +]; const basePath = '/1.0/resolve/identifiers/:did'; cfg.routes = {basePath}; diff --git a/lib/didResolver.js b/lib/didResolver.js index da3fbbd..a96d4f2 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -1,11 +1,14 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ +import { + validateDidRequest, + validateRequestOptions +} from './validators/index.js'; import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import {getDidKeyComponents} from './didComponents.js'; import logger from './logger.js'; -import {validateDidRequest} from './validators/index.js'; /** * Resolves a did after validating it. @@ -14,11 +17,18 @@ import {validateDidRequest} from './validators/index.js'; * @param {string} options.did - The did or didUrl being resolved. * @param {Array} options.supportedMethods - A list of * did methods this resolver supports. + * @param {Array} options.publicKeyFormats - A list of + * public key formats this resolver supports. * @param {object} options.didOptions - Options passed to the resolver. * * @returns {object} - Returns a status code and the resolution data. */ -export const resolveDid = async ({did, supportedMethods, didOptions}) => { +export const resolveDid = async ({ + did, + supportedMethods, + publicKeyFormats, + didOptions +}) => { let isDidUrl = false; let error; try { @@ -26,6 +36,14 @@ export const resolveDid = async ({did, supportedMethods, didOptions}) => { isDidUrl = didKeyComponents.isDidUrl; await validateDidRequest({didKeyComponents, supportedMethods, didOptions}); const didDocument = await didIo.get({did}); + // FIXME didOptions should probably be in didIo + // in the future + validateRequestOptions({ + did, + didDocument, + didOptions, + publicKeyFormats + }); return { statusCode: 200, resolutionResult: new DidResolutionResult({didDocument, isDidUrl}) diff --git a/lib/http.js b/lib/http.js index a0d5c16..51b89f3 100644 --- a/lib/http.js +++ b/lib/http.js @@ -9,7 +9,11 @@ import {resolveDid} from './didResolver.js'; const {config} = bedrock; bedrock.events.on('bedrock-express.configure.routes', app => { - const {routes, supportedMethods} = config['bedrock-did-resolver-http']; + const { + routes, + supportedMethods, + publicKeyFormats + } = config['bedrock-did-resolver-http']; app.get( routes.basePath, asyncHandler(async (req, res) => { @@ -18,10 +22,12 @@ bedrock.events.on('bedrock-express.configure.routes', app => { // except some are headers on the request const didOptions = {...req.headers, ...req.query}; try { - const { - statusCode, - resolutionResult - } = await resolveDid({did, supportedMethods, didOptions}); + const {statusCode, resolutionResult} = await resolveDid({ + did, + supportedMethods, + publicKeyFormats, + didOptions + }); res.status(statusCode).json(resolutionResult); } catch(error) { logger.error(`Did resolution error ${did}`, {error}); diff --git a/lib/validators/didResolverErrors.js b/lib/validators/didResolverErrors.js new file mode 100644 index 0000000..7bf6686 --- /dev/null +++ b/lib/validators/didResolverErrors.js @@ -0,0 +1,28 @@ +/*! + * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. + */ + +export class MethodNotSupported extends Error { + constructor(message) { + super(message); + this.name = 'MethodNotSupportedError'; + this.code = 'methodNotSupported'; + } +} + +export class NotFound extends Error { + constructor(message) { + super(message); + this.name = 'NotFoundError'; + this.code = 'notFound'; + } +} + +export class InternalError extends Error { + constructor(message) { + super(message); + this.name = 'InternalError'; + this.code = 'internalError'; + } +} + diff --git a/lib/validators/index.js b/lib/validators/index.js index e885768..91c8e7b 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -4,6 +4,7 @@ import * as didErrors from './didErrors.js'; import * as didKeyErrors from './didKeyErrors.js'; +import * as didResolverErrors from './didResolverErrors.js'; import {IdDecoder} from 'bnid'; const idDecoder = new IdDecoder(); @@ -18,8 +19,6 @@ const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); * various did components. * @param {Array} options.supportedMethods - A list of * did:key methods this implementation supports. - * @param {object} options.didOptions - Options passed - * in the request. * * @throws {Error} Throws did related errors. * @@ -30,23 +29,106 @@ const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); export const validateDidRequest = ({ didKeyComponents, supportedMethods, - didOptions }) => { const {scheme, method, version, multibase, did, isDidUrl} = didKeyComponents; + // this is for conformance with the did resolver spec + if(!supportedMethods.includes(method)) { + throw new didResolverErrors.MethodNotSupported( + `Unsupported method ${method}`); + } _validateDid({scheme, did, isDidUrl}); if((isDidUrl && !_isValidDidUrl({didUrl: did}))) { throw new didErrors.InvalidDidUrl(`Invalid didUrl ${did}`); } - _validateRequestOptions({supportedMethods, didOptions, didKeyComponents}); _validateDidKey({method, version, multibase}); _validateMultibaseEd25519({didKeyComponents}); }; -function _validateRequestOptions({ - supportedMethods, - didOptions, -}) { +// FIXME while we do validate public key formats we do not +// convert to the requested public key format. +/** + * Public did:keys can be represented in multiple formats. + * While we don't do any conversion in this library we still make + * the check. + * + * @param {object} options - Options to use. + * @param {Array} options.publicKeyFormats - A list of + * public key formats our did:key implementation supports. + * @param {object} options.didOptions - The didOptions from searchParams + * and headers. + * @param {string} options.didOptions.publicKeyFormat - The format + * the public key should be returned in. + * @param {string} options.didOptions.enableExperimentalPublicKeyTypes - An + * option that can be passed in to allow experimental key types. + * @param {object} options.didDocument - The didDocument requred by the did + * or didUrl. + * + * @throws {Error} Throws UnsupportedPublicKeyType or InvalidPublicKeyType. + * + * @returns {undefined} Returns on sucess. + */ +export const validateRequestOptions = ({ + publicKeyFormats = [], + didOptions: { + publicKeyFormat, + enableExperimentalPublicKeyTypes = false + } = {}, + didDocument, +}) => { + // if no publicKeyFormat was in the request just skip this check + if(!publicKeyFormat) { + return; + } + // supported public key formats are set manually on config + if(!publicKeyFormats.includes(publicKeyFormat)) { + throw new didKeyErrors.UnsupportedPublicKeyType( + `Unsupported public key type ${publicKeyFormat}`); + } + // all of the other did methods so far are signature verification + if(!_convertSearchParamToBoolean({param: enableExperimentalPublicKeyTypes})) { + const verificationFormats = ['Multikey', 'JsonWebKey2020']; + //keyAgreement is an encryption verification method + if(didDocument.type.includes('KeyAgreementKey')) { + const encryptionFormats = [ + ...verificationFormats, + 'X25519KeyAgreementKey2020' + ]; + if(!encryptionFormats.includes(publicKeyFormat)) { + throw new didKeyErrors.InvalidPublicKeyType( + `Invalid Public Key Type ${publicKeyFormat}`); + } + // no further checks needed + return; + } + const signatureFormats = [ + ...verificationFormats, + 'Ed25519VerificationKey2020' + ]; + if(!signatureFormats.includes(publicKeyFormat)) { + throw new didKeyErrors.InvalidPublicKeyType( + `Invalid Public Key Type ${publicKeyFormat}`); + } + } +}; +/** + * Search Params are encoded as strings so this turns + * strings of value '0' or 'false' into false. + * + * @param {object} options - Options to use. + * @param {string} options.param - The param value. + * + * @returns {boolean} Returns a boolean. + */ +function _convertSearchParamToBoolean({param}) { + if(typeof param !== 'string') { + return Boolean(param); + } + const falsey = /0|false/i; + if(falsey.test(param)) { + return false; + } + return true; } /** @@ -89,6 +171,16 @@ function _validateDid({scheme, did, isDidUrl}) { } } +/** + * A version must be convertible to a positive integer. + * + * @param {object} options - Options to use. + * @param {string|number} options.version - A did:key:version. + * + * @throws {Error} Throws InvalidDid. + * + * @returns {undefined} Returns on success. + */ function _validateVersion({version}) { try { const versionNumber = Number.parseInt(version); @@ -100,12 +192,11 @@ function _validateVersion({version}) { throw new didErrors.InvalidDid( `Version must be a number received "${version}"`); } - return true; } // FIXME we should probably refactor // ed25519-verification-key-2020/lib/Ed25519VerificationKey2020.js -// to use these newer errors +// to use these newer errors the check is already there function _validateMultibaseEd25519({didKeyComponents}) { const {multibase} = didKeyComponents; const publicKeyBytes = idDecoder.decode(multibase). From 38bc2cf49e8712e0a7205c32bd24d823922243ab Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 14:28:20 -0400 Subject: [PATCH 18/55] Update github actions node versions to 14 and 16 + .npmrc for legacy peer deps. --- .github/workflows/main.yml | 2 +- .npmrc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .npmrc diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 28e9f44..415625f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - 27017:27017 strategy: matrix: - node-version: [12.x, 14.x] + node-version: [14.x, 16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true From 98b22121abff33a6dee90a6429fb396ac7dadb86 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 14:33:30 -0400 Subject: [PATCH 19/55] Add .npmrc to test project. --- test/.npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/.npmrc diff --git a/test/.npmrc b/test/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/test/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true From cb0c52e6b1031f02e0ed8220bc98908c31cad293 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 14:37:44 -0400 Subject: [PATCH 20/55] Upgrade github workflows to mongodb 4.4 add more install steps. --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 415625f..e3a6242 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: timeout-minutes: 10 services: mongodb: - image: mongo:4.2 + image: mongo:4.4 ports: - 27017:27017 strategy: @@ -37,6 +37,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: | + npm install cd test npm install - name: Run test with Node.js ${{ matrix.node-version }} @@ -49,7 +50,7 @@ jobs: timeout-minutes: 10 services: mongodb: - image: mongo:4.2 + image: mongo:4.4 ports: - 27017:27017 strategy: @@ -62,6 +63,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: | + npm install cd test npm install - name: Generate coverage report From ed6989f65208c35fd72413f440ef9d33778ab29f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 14:56:15 -0400 Subject: [PATCH 21/55] Add statusCodes to errors. --- lib/validators/didErrors.js | 2 ++ lib/validators/didKeyErrors.js | 4 ++++ lib/validators/didResolverErrors.js | 3 +++ 3 files changed, 9 insertions(+) diff --git a/lib/validators/didErrors.js b/lib/validators/didErrors.js index a0693ff..e14a24a 100644 --- a/lib/validators/didErrors.js +++ b/lib/validators/didErrors.js @@ -9,6 +9,7 @@ export class InvalidDid extends Error { super(message); this.name = 'InvalidDidError'; this.code = 'invalidDid'; + this.statusCode = 400; } } @@ -17,5 +18,6 @@ export class InvalidDidUrl extends Error { super(message); this.name = 'InvalidDidUrlError'; this.code = 'invalidDidUrl'; + this.statusCode = 400; } } diff --git a/lib/validators/didKeyErrors.js b/lib/validators/didKeyErrors.js index bc8e1ee..7f1fe88 100644 --- a/lib/validators/didKeyErrors.js +++ b/lib/validators/didKeyErrors.js @@ -9,6 +9,7 @@ export class InvalidPublicKey extends Error { super(message); this.name = 'InvalidPublicKeyError'; this.code = 'invalidPublicKey'; + this.statusCode = 400; } } @@ -17,6 +18,7 @@ export class InvalidPublicKeyLength extends Error { super(message); this.name = 'InvalidPublicKeyLengthError'; this.code = 'invalidPublicKeyLength'; + this.statusCode = 400; } } @@ -25,6 +27,7 @@ export class InvalidPublicKeyType extends Error { super(message); this.name = 'InvalidPublicKeyTypeError'; this.code = 'invalidPublicKeyType'; + this.statusCode = 400; } } @@ -33,5 +36,6 @@ export class UnsupportedPublicKeyType extends Error { super(message); this.name = 'UnsupportedPublicKeyTypeError'; this.code = 'unsupportedPublicKeyType'; + this.statusCode = 400; } } diff --git a/lib/validators/didResolverErrors.js b/lib/validators/didResolverErrors.js index 7bf6686..58ec2c0 100644 --- a/lib/validators/didResolverErrors.js +++ b/lib/validators/didResolverErrors.js @@ -7,6 +7,7 @@ export class MethodNotSupported extends Error { super(message); this.name = 'MethodNotSupportedError'; this.code = 'methodNotSupported'; + this.statusCode = 400; } } @@ -15,6 +16,7 @@ export class NotFound extends Error { super(message); this.name = 'NotFoundError'; this.code = 'notFound'; + this.statusCode = 404; } } @@ -23,6 +25,7 @@ export class InternalError extends Error { super(message); this.name = 'InternalError'; this.code = 'internalError'; + this.statusCode = 500; } } From e2299eab2f3ed4eb207dc35de187e08f87e918c9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 15:49:17 -0400 Subject: [PATCH 22/55] Add basic tests to test project. --- test/mocha/10-api.js | 28 +++++++++++++++++++++++++--- test/mocha/assertions.js | 27 +++++++++++++++++++++++++++ test/mocha/helpers.js | 18 ++++++++++++++++++ test/test.config.js | 5 +++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 test/mocha/assertions.js create mode 100644 test/mocha/helpers.js diff --git a/test/mocha/10-api.js b/test/mocha/10-api.js index 6157d46..ff0100b 100644 --- a/test/mocha/10-api.js +++ b/test/mocha/10-api.js @@ -1,7 +1,29 @@ /* - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ +import {makeRequest} from './helpers.js'; +import {shouldBeDidResolverResponse} from './assertions.js'; -describe('api', () => { - it('should work'); +// default valid bs58 ed25519 did +const did = 'did:key:z6MktKwz7Ge1Yxzr4JHavN33wiwa8y81QdcMRLXQsrH9T53b'; +// The id of the keyAgreementKey used for encryption verification +const encryptionId = 'did:key:z6MktKwz7Ge1Yxzr4JHavN33wiwa8y81QdcMRLXQsrH9T5' + + '3b#z6LSfHfAMAopsuBxaBzvp51GXrPf38Az13j2fmwqadbwwrzJ'; +// The id of the verificationMethod used for signature verification +const signatureId = 'did:key:z6MktKwz7Ge1Yxzr4JHavN33wiwa8y81QdcMRLXQsrH9T53' + + 'b#z6MktKwz7Ge1Yxzr4JHavN33wiwa8y81QdcMRLXQsrH9T53b'; + +describe('api', function() { + it('should resolve did', async function() { + const result = await makeRequest({did}); + shouldBeDidResolverResponse(result); + }); + it('should resolve signature verification method', async function() { + const result = await makeRequest({did: signatureId}); + shouldBeDidResolverResponse(result); + }); + it('should resolve encryption verification method', async function() { + const result = await makeRequest({did: encryptionId}); + shouldBeDidResolverResponse(result); + }); }); diff --git a/test/mocha/assertions.js b/test/mocha/assertions.js new file mode 100644 index 0000000..7e05972 --- /dev/null +++ b/test/mocha/assertions.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. + */ + +/** + * Tests the properties of a did resolver return value. + * + * @param {object} data - The data from a did resolver. + * + * @returns {undefined} Just returns on success. + */ +export const shouldBeDidResolverResponse = data => { + const resolverResponse = 'DID Resolver response'; + should.exist(data, `Expected ${resolverResponse} to have data.`); + data.should.be.an( + 'object', + `Expected ${resolverResponse} data to be an object` + ); + data.should.have.property('didDocument'); + data.should.have.property('@context'); + data.should.have.property('didDocumentMetadata'); + const didMetaData = data.didDereferencingMetadata || + data.didResolutionMetadata; + should.exist(didMetaData, 'Expected didResolver data to have either' + + ' "didDereferencingMetadata" or "didResolutionMetadata"'); +}; + diff --git a/test/mocha/helpers.js b/test/mocha/helpers.js new file mode 100644 index 0000000..8626cad --- /dev/null +++ b/test/mocha/helpers.js @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. + */ + +import {agent} from '@bedrock/https-agent'; +import {httpClient} from '@digitalbazaar/http-client'; + +const headers = { + Accept: 'application/ld+json;profile="https://w3id.org/did-resolution"' +}; + +export const makeRequest = async ({did}) => { + const basePath = '/1.0/resolve/identifiers/'; + const url = `https://localhost:52443${basePath}${encodeURIComponent(did)}`; + const {data} = await httpClient.get(url, {headers, agent}); + return data; +}; + diff --git a/test/test.config.js b/test/test.config.js index 30e4900..6755195 100644 --- a/test/test.config.js +++ b/test/test.config.js @@ -17,3 +17,8 @@ config.mocha.tests.push(path.join(__dirname, 'mocha')); // allow self-signed certs in test framework config['https-agent'].rejectUnauthorized = false; + +// server info +config.server.port = 52443; +config.server.httpPort = 52080; +config.server.domain = 'localhost'; From ad3961092e9b4b338f1f8cf6f75eafa3c9a52f28 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 15:54:47 -0400 Subject: [PATCH 23/55] Use c8 for coverage. --- test/package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/package.json b/test/package.json index c4f3c83..ee3d0d0 100644 --- a/test/package.json +++ b/test/package.json @@ -5,20 +5,20 @@ "type": "module", "scripts": { "test": "node --preserve-symlinks test.js test", - "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text-summary npm test", - "coverage-ci": "cross-env NODE_ENV=test nyc --reporter=lcovonly npm test", - "coverage-report": "nyc report" + "coverage": "cross-env NODE_ENV=test c8 --reporter=lcov --reporter=text-summary npm test", + "coverage-ci": "cross-env NODE_ENV=test c8 --reporter=lcovonly npm test", + "coverage-report": "c8 report" }, "dependencies": { "@bedrock/core": "^6.0.0", - "bedrock-did-resolver-http": "file:..", "@bedrock/express": "^8.0.0", "@bedrock/https-agent": "^4.0.0", "@bedrock/mongodb": "^10.0.0", "@bedrock/server": "^5.0.0", "@bedrock/test": "^8.0.0", - "cross-env": "^7.0.3", - "nyc": "^15.1.0" + "bedrock-did-resolver-http": "file:..", + "c8": "^7.12.0", + "cross-env": "^7.0.3" }, "nyc": { "excludeNodeModules": false, From 7e69c10e422fffc30d824b1ea76675d4d2ceadde Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 16:29:39 -0400 Subject: [PATCH 24/55] Fully capitalize JSON-LD. Co-authored-by: Manu Sporny --- lib/DidResolutionResult.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DidResolutionResult.js b/lib/DidResolutionResult.js index 79bce8e..d85aed1 100644 --- a/lib/DidResolutionResult.js +++ b/lib/DidResolutionResult.js @@ -3,7 +3,7 @@ */ /** - * Creates the json-ld for the response. + * Creates the JSON-LD for the response. * * @param {object} options - Options to use. * @param {boolean} [options.isDidUrl=false] - Does the response need From c40562cbccf967628ce494d8515a9de3d8e7f621 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 16:31:59 -0400 Subject: [PATCH 25/55] Capitalize DID in error message. Co-authored-by: Manu Sporny --- lib/http.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 51b89f3..f84eccf 100644 --- a/lib/http.js +++ b/lib/http.js @@ -30,7 +30,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => { }); res.status(statusCode).json(resolutionResult); } catch(error) { - logger.error(`Did resolution error ${did}`, {error}); + logger.error(`DID resolution error ${did}`, {error}); throw error; } })); From 629b79a8bf970c2e5d9e67fc4753305164761aae Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 16:35:30 -0400 Subject: [PATCH 26/55] Separate publicKey into public key. Co-authored-by: Manu Sporny --- lib/validators/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validators/index.js b/lib/validators/index.js index 91c8e7b..79b70fd 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -203,7 +203,7 @@ function _validateMultibaseEd25519({didKeyComponents}) { slice(MULTICODEC_ED25519_PUB_HEADER.length); if(publicKeyBytes.length !== 32) { throw new didKeyErrors.InvalidPublicKeyLength( - 'Expected 32 byte publicKey.'); + 'Expected 32 byte public key.'); } } From ef6af7a05a487a9008d9a5a39a6220551c160256 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Aug 2022 17:40:32 -0400 Subject: [PATCH 27/55] Expect whole thing and not part of a string. --- lib/validators/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validators/index.js b/lib/validators/index.js index 79b70fd..c29da9a 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -88,7 +88,7 @@ export const validateRequestOptions = ({ if(!_convertSearchParamToBoolean({param: enableExperimentalPublicKeyTypes})) { const verificationFormats = ['Multikey', 'JsonWebKey2020']; //keyAgreement is an encryption verification method - if(didDocument.type.includes('KeyAgreementKey')) { + if(didDocument.type === 'X25519KeyAgreementKey2020') { const encryptionFormats = [ ...verificationFormats, 'X25519KeyAgreementKey2020' From 177f622999f1fdb39e226b092a4142c78b08cb4d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 5 Aug 2022 14:36:38 -0400 Subject: [PATCH 28/55] Remove validators moved to did-io. --- lib/validators/index.js | 80 +---------------------------------------- package.json | 2 +- 2 files changed, 2 insertions(+), 80 deletions(-) diff --git a/lib/validators/index.js b/lib/validators/index.js index c29da9a..6b48c35 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -5,11 +5,6 @@ import * as didErrors from './didErrors.js'; import * as didKeyErrors from './didKeyErrors.js'; import * as didResolverErrors from './didResolverErrors.js'; -import {IdDecoder} from 'bnid'; - -const idDecoder = new IdDecoder(); -// multicodec ed25519-pub header as Uint8Array -const MULTICODEC_ED25519_PUB_HEADER = new Uint8Array([0xed, 0x01]); /** * Validates the components of a did. @@ -30,18 +25,13 @@ export const validateDidRequest = ({ didKeyComponents, supportedMethods, }) => { - const {scheme, method, version, multibase, did, isDidUrl} = didKeyComponents; + const {method, version, multibase} = didKeyComponents; // this is for conformance with the did resolver spec if(!supportedMethods.includes(method)) { throw new didResolverErrors.MethodNotSupported( `Unsupported method ${method}`); } - _validateDid({scheme, did, isDidUrl}); - if((isDidUrl && !_isValidDidUrl({didUrl: did}))) { - throw new didErrors.InvalidDidUrl(`Invalid didUrl ${did}`); - } _validateDidKey({method, version, multibase}); - _validateMultibaseEd25519({didKeyComponents}); }; // FIXME while we do validate public key formats we do not @@ -160,17 +150,6 @@ function _validateDidKey({ _validateVersion({version}); } -function _validateDid({scheme, did, isDidUrl}) { - // this is more specific than just invalidDid - if(scheme !== 'did') { - throw new didErrors.InvalidDid(`Scheme must be "did" received "${scheme}"`); - } - // this will catch invalid dids such as did:key:@@ - if(!isDidUrl && !_isValidDid({did})) { - throw new didErrors.InvalidDid(`Invalid did ${did}`); - } -} - /** * A version must be convertible to a positive integer. * @@ -193,60 +172,3 @@ function _validateVersion({version}) { `Version must be a number received "${version}"`); } } - -// FIXME we should probably refactor -// ed25519-verification-key-2020/lib/Ed25519VerificationKey2020.js -// to use these newer errors the check is already there -function _validateMultibaseEd25519({didKeyComponents}) { - const {multibase} = didKeyComponents; - const publicKeyBytes = idDecoder.decode(multibase). - slice(MULTICODEC_ED25519_PUB_HEADER.length); - if(publicKeyBytes.length !== 32) { - throw new didKeyErrors.InvalidPublicKeyLength( - 'Expected 32 byte public key.'); - } -} - -/** - * This function comes from the did-test-suite. - * - * @see https://github.com/w3c/did-test-suite/ - * - * @param {object} options - Options to use. - * @param {string} options.did - A prospective did. - * - * @returns {boolean} - Returns true or false. -*/ -function _isValidDid({did}) { - const didRegex1 = new RegExp('^did:(?[a-z0-9]+):' + - '(?([a-zA-Z0-9\\.\\-_]|%[0-9a-fA-F]{2}|:)+$)'); - const didRegex2 = /:$/; - return didRegex1.test(did) && !didRegex2.test(did); -} - -/** - * This function comes from the did-test-suite. - * - * @see https://github.com/w3c/did-test-suite/ - * - * @param {object} options - Options to use. - * @param {string} options.didUrl - A prospective didUrl. - * - * @returns {boolean} - Returns true or false. -*/ -function _isValidDidUrl({didUrl}) { - const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; - const didUrlPattern = - '^' + - 'did:' + - '([a-z0-9]+)' + // method_name - '(:' + // method-specific-id - '([a-zA-Z0-9\\.\\-_]|%[0-9a-fA-F]{2})+' + - ')+' + - '((/(' + pchar + ')+)+)?' + // path-abempty - '(\\?(' + pchar + '|/|\\?)+)?' + // [ "?" query ] - '(#(' + pchar + '|/|\\?)+)?' + // [ "#" fragment ] - '$' - ; - return new RegExp(didUrlPattern).test(didUrl); -} diff --git a/package.json b/package.json index f214ecf..d862450 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "homepage": "https://github.com/digitalbazaar/bedrock-did-resolver-http", "dependencies": { - "@bedrock/did-io": "^9.0.1", + "@bedrock/did-io": "digitalbazaar/bedrock-did-io#use-next-release-did-io", "bnid": "^3.0.0" }, "peerDependencies": { From bc44d0b9afb84bdf0de1f69fc9313de8b0b061cd Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 5 Aug 2022 15:19:12 -0400 Subject: [PATCH 29/55] Remove did key related validators. --- lib/validators/index.js | 56 +---------------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/lib/validators/index.js b/lib/validators/index.js index 6b48c35..9756996 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -2,7 +2,6 @@ * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import * as didErrors from './didErrors.js'; import * as didKeyErrors from './didKeyErrors.js'; import * as didResolverErrors from './didResolverErrors.js'; @@ -25,13 +24,12 @@ export const validateDidRequest = ({ didKeyComponents, supportedMethods, }) => { - const {method, version, multibase} = didKeyComponents; + const {method} = didKeyComponents; // this is for conformance with the did resolver spec if(!supportedMethods.includes(method)) { throw new didResolverErrors.MethodNotSupported( `Unsupported method ${method}`); } - _validateDidKey({method, version, multibase}); }; // FIXME while we do validate public key formats we do not @@ -120,55 +118,3 @@ function _convertSearchParamToBoolean({param}) { } return true; } - -/** - * General validation for did:keys independent - * of key type specific validation. - * - * @param {object} options - Options to use. - * @param {string} options.method - A did:method. - * @param {string|number} options.version - A did:method:version. - * @param {string} [options.multibase = ''] - The multibase value - * of the did:key. - * - * @throws {Error} Throws general did:key errors. - * - * @returns {undefined} If the didKeyComponents are valid. - */ -function _validateDidKey({ - method, - version, - multibase = '', -}) { - if(method !== 'key') { - throw new didErrors.InvalidDid(`Method must be "key" received "${method}"`); - } - if(!multibase.startsWith('z')) { - throw new didErrors.InvalidDid( - `Multibase must start with "z" received ${multibase[0]}`); - } - _validateVersion({version}); -} - -/** - * A version must be convertible to a positive integer. - * - * @param {object} options - Options to use. - * @param {string|number} options.version - A did:key:version. - * - * @throws {Error} Throws InvalidDid. - * - * @returns {undefined} Returns on success. - */ -function _validateVersion({version}) { - try { - const versionNumber = Number.parseInt(version); - if(versionNumber <= 0) { - throw new didErrors.InvalidDid( - `Version must be a positive integer received "${versionNumber}"`); - } - } catch(e) { - throw new didErrors.InvalidDid( - `Version must be a number received "${version}"`); - } -} From 02c977cf4598423889e7acdf6b49c7cf0b8f6dfe Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 16:03:14 -0400 Subject: [PATCH 30/55] Remove lib/didComponents, didErrors, & bnid. --- lib/didComponents.js | 79 ----------------------------- lib/didResolver.js | 18 ++++--- lib/validators/didErrors.js | 23 --------- lib/validators/didKeyErrors.js | 4 -- lib/validators/didResolverErrors.js | 3 -- lib/validators/index.js | 7 ++- package.json | 3 +- 7 files changed, 14 insertions(+), 123 deletions(-) delete mode 100644 lib/didComponents.js delete mode 100644 lib/validators/didErrors.js diff --git a/lib/didComponents.js b/lib/didComponents.js deleted file mode 100644 index a0efc5c..0000000 --- a/lib/didComponents.js +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. - */ - -/** - * Using a colon (:) as the delimiter, split the identifier - * into its components: a scheme, a method, a version, and a - * multibaseValue. If there are only three components set the - * version to the string value 1 and use the last value as - * the multibaseValue. - * - * @param {object} options - Options to use. - * @param {string} options.did - A did as a string. - * - * @returns {object} The components of the did. -*/ -export const getDidKeyComponents = ({did}) => { - const { - isDidUrl, - didEndIndex, - fragment, - search, - searchParams - } = _getUrlComponents({did}); - // the identifier should be the did before the url syntax starts - const identifier = isDidUrl ? did.substring(0, didEndIndex) : did; - // split on : unless : is the last character in a did url /(?!:$):/ - const parts = identifier.split(/(?!:$):/); - const [scheme, method, version, multibase] = parts; - return { - scheme, - method, - // if multibase exists use the version - version: multibase ? version : '1', - // multibase maybe undefined - // if multibase exists use multibase - multibase: multibase || version, - parts, - did, - identifier, - // contains the fragment to dereference - fragment, - // contains the search string - search, - // contains a map with key value pairs - searchParams, - isDidUrl - }; -}; - -/** - * If the did is a did url it gets the fragment and searchParams. - * - * @private - * - * @param {object} options - Options to use. - * @param {string} options.did - A potential did url. - * - * @returns {object} The resulting url components. - */ -function _getUrlComponents({did}) { - // if there is a fragment `#` or a service `?service` or it contains / - // then we are dereferencing a did url - const urlParts = /[\/#?]/; - const urlMatch = urlParts.exec(did); - if(urlMatch) { - const urlParts = new URL(did); - return { - isDidUrl: true, - fragment: urlParts.hash, - search: urlParts.search, - searchParams: urlParts.searchParams, - didEndIndex: urlMatch.index - }; - } - return { - isDidUrl: false - }; -} diff --git a/lib/didResolver.js b/lib/didResolver.js index a96d4f2..1c6326e 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -1,13 +1,12 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ +import {didIo, isDidUrl, parseDid} from '@bedrock/did-io'; import { validateDidRequest, validateRequestOptions } from './validators/index.js'; -import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; -import {getDidKeyComponents} from './didComponents.js'; import logger from './logger.js'; /** @@ -29,12 +28,12 @@ export const resolveDid = async ({ publicKeyFormats, didOptions }) => { - let isDidUrl = false; + let _isDidUrl = false; let error; try { - const didKeyComponents = getDidKeyComponents({did}); - isDidUrl = didKeyComponents.isDidUrl; - await validateDidRequest({didKeyComponents, supportedMethods, didOptions}); + _isDidUrl = isDidUrl({did}); + const {method} = parseDid({did}); + await validateDidRequest({method, supportedMethods, didOptions}); const didDocument = await didIo.get({did}); // FIXME didOptions should probably be in didIo // in the future @@ -46,7 +45,10 @@ export const resolveDid = async ({ }); return { statusCode: 200, - resolutionResult: new DidResolutionResult({didDocument, isDidUrl}) + resolutionResult: new DidResolutionResult({ + didDocument, + isDidUrl: _isDidUrl + }) }; } catch(e) { logger.error(`did resolution error: ${did}`, {error: e}); @@ -55,7 +57,7 @@ export const resolveDid = async ({ if(error) { return { statusCode: error.statusCode || 500, - resolutionResult: new DidResolutionResult({error, isDidUrl}) + resolutionResult: new DidResolutionResult({error, isDidUrl: _isDidUrl}) }; } }; diff --git a/lib/validators/didErrors.js b/lib/validators/didErrors.js deleted file mode 100644 index e14a24a..0000000 --- a/lib/validators/didErrors.js +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. - */ - -//FIXME these errors should probably be in did-method-key - -export class InvalidDid extends Error { - constructor(message) { - super(message); - this.name = 'InvalidDidError'; - this.code = 'invalidDid'; - this.statusCode = 400; - } -} - -export class InvalidDidUrl extends Error { - constructor(message) { - super(message); - this.name = 'InvalidDidUrlError'; - this.code = 'invalidDidUrl'; - this.statusCode = 400; - } -} diff --git a/lib/validators/didKeyErrors.js b/lib/validators/didKeyErrors.js index 7f1fe88..bc8e1ee 100644 --- a/lib/validators/didKeyErrors.js +++ b/lib/validators/didKeyErrors.js @@ -9,7 +9,6 @@ export class InvalidPublicKey extends Error { super(message); this.name = 'InvalidPublicKeyError'; this.code = 'invalidPublicKey'; - this.statusCode = 400; } } @@ -18,7 +17,6 @@ export class InvalidPublicKeyLength extends Error { super(message); this.name = 'InvalidPublicKeyLengthError'; this.code = 'invalidPublicKeyLength'; - this.statusCode = 400; } } @@ -27,7 +25,6 @@ export class InvalidPublicKeyType extends Error { super(message); this.name = 'InvalidPublicKeyTypeError'; this.code = 'invalidPublicKeyType'; - this.statusCode = 400; } } @@ -36,6 +33,5 @@ export class UnsupportedPublicKeyType extends Error { super(message); this.name = 'UnsupportedPublicKeyTypeError'; this.code = 'unsupportedPublicKeyType'; - this.statusCode = 400; } } diff --git a/lib/validators/didResolverErrors.js b/lib/validators/didResolverErrors.js index 58ec2c0..7bf6686 100644 --- a/lib/validators/didResolverErrors.js +++ b/lib/validators/didResolverErrors.js @@ -7,7 +7,6 @@ export class MethodNotSupported extends Error { super(message); this.name = 'MethodNotSupportedError'; this.code = 'methodNotSupported'; - this.statusCode = 400; } } @@ -16,7 +15,6 @@ export class NotFound extends Error { super(message); this.name = 'NotFoundError'; this.code = 'notFound'; - this.statusCode = 404; } } @@ -25,7 +23,6 @@ export class InternalError extends Error { super(message); this.name = 'InternalError'; this.code = 'internalError'; - this.statusCode = 500; } } diff --git a/lib/validators/index.js b/lib/validators/index.js index 9756996..e2936ba 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -9,8 +9,8 @@ import * as didResolverErrors from './didResolverErrors.js'; * Validates the components of a did. * * @param {object} options - Options to use. - * @param {object} options.didKeyComponents - An object with - * various did components. + * @param {string} options.method - The did method of the did + * being resolved. * @param {Array} options.supportedMethods - A list of * did:key methods this implementation supports. * @@ -21,10 +21,9 @@ import * as didResolverErrors from './didResolverErrors.js'; // fixme this should probably be moved into the did-method-key // get function and be run before returning a didDocument export const validateDidRequest = ({ - didKeyComponents, + method, supportedMethods, }) => { - const {method} = didKeyComponents; // this is for conformance with the did resolver spec if(!supportedMethods.includes(method)) { throw new didResolverErrors.MethodNotSupported( diff --git a/package.json b/package.json index d862450..62015f3 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,7 @@ }, "homepage": "https://github.com/digitalbazaar/bedrock-did-resolver-http", "dependencies": { - "@bedrock/did-io": "digitalbazaar/bedrock-did-io#use-next-release-did-io", - "bnid": "^3.0.0" + "@bedrock/did-io": "digitalbazaar/bedrock-did-io#use-next-release-did-io" }, "peerDependencies": { "@bedrock/core": "^6.0.0", From ae7762958e6a912f67fc9571e3daeaf4d7c447f8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 16:09:55 -0400 Subject: [PATCH 31/55] Add @db/did-io to peerDeps & isDidUrl & parseDid. --- lib/didResolver.js | 3 ++- package.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 1c6326e..0fa374a 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -1,11 +1,12 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import {didIo, isDidUrl, parseDid} from '@bedrock/did-io'; +import {isDidUrl, parseDid} from '@digitalbazaar/did-io'; import { validateDidRequest, validateRequestOptions } from './validators/index.js'; +import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import logger from './logger.js'; diff --git a/package.json b/package.json index 62015f3..2a6ee40 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@bedrock/did-io": "digitalbazaar/bedrock-did-io#use-next-release-did-io" }, "peerDependencies": { + "@digitalbazaar/did-io": "^2.0.0", "@bedrock/core": "^6.0.0", "@bedrock/express": "^8.0.0" }, From a47725f38bd84e6528e2adeab18eea13df8870ea Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 16:16:32 -0400 Subject: [PATCH 32/55] Remove didKey and didResolution error files in favor of DidResolutionError from did-io. --- lib/validators/didKeyErrors.js | 37 ----------------------------- lib/validators/didResolverErrors.js | 28 ---------------------- lib/validators/index.js | 27 +++++++++++++-------- 3 files changed, 17 insertions(+), 75 deletions(-) delete mode 100644 lib/validators/didKeyErrors.js delete mode 100644 lib/validators/didResolverErrors.js diff --git a/lib/validators/didKeyErrors.js b/lib/validators/didKeyErrors.js deleted file mode 100644 index bc8e1ee..0000000 --- a/lib/validators/didKeyErrors.js +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. - */ - -//FIXME these errors should probably be in did-method-key - -export class InvalidPublicKey extends Error { - constructor(message) { - super(message); - this.name = 'InvalidPublicKeyError'; - this.code = 'invalidPublicKey'; - } -} - -export class InvalidPublicKeyLength extends Error { - constructor(message) { - super(message); - this.name = 'InvalidPublicKeyLengthError'; - this.code = 'invalidPublicKeyLength'; - } -} - -export class InvalidPublicKeyType extends Error { - constructor(message) { - super(message); - this.name = 'InvalidPublicKeyTypeError'; - this.code = 'invalidPublicKeyType'; - } -} - -export class UnsupportedPublicKeyType extends Error { - constructor(message) { - super(message); - this.name = 'UnsupportedPublicKeyTypeError'; - this.code = 'unsupportedPublicKeyType'; - } -} diff --git a/lib/validators/didResolverErrors.js b/lib/validators/didResolverErrors.js deleted file mode 100644 index 7bf6686..0000000 --- a/lib/validators/didResolverErrors.js +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. - */ - -export class MethodNotSupported extends Error { - constructor(message) { - super(message); - this.name = 'MethodNotSupportedError'; - this.code = 'methodNotSupported'; - } -} - -export class NotFound extends Error { - constructor(message) { - super(message); - this.name = 'NotFoundError'; - this.code = 'notFound'; - } -} - -export class InternalError extends Error { - constructor(message) { - super(message); - this.name = 'InternalError'; - this.code = 'internalError'; - } -} - diff --git a/lib/validators/index.js b/lib/validators/index.js index e2936ba..f3403b8 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -2,8 +2,7 @@ * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import * as didKeyErrors from './didKeyErrors.js'; -import * as didResolverErrors from './didResolverErrors.js'; +import {DidResolverError} from '@digitalbazaar/did-io'; /** * Validates the components of a did. @@ -26,8 +25,10 @@ export const validateDidRequest = ({ }) => { // this is for conformance with the did resolver spec if(!supportedMethods.includes(method)) { - throw new didResolverErrors.MethodNotSupported( - `Unsupported method ${method}`); + throw new DidResolverError({ + message: `Unsupported method ${method}`, + code: 'methodNotSupported' + }); } }; @@ -68,8 +69,10 @@ export const validateRequestOptions = ({ } // supported public key formats are set manually on config if(!publicKeyFormats.includes(publicKeyFormat)) { - throw new didKeyErrors.UnsupportedPublicKeyType( - `Unsupported public key type ${publicKeyFormat}`); + throw new DidResolverError({ + message: `Unsupported public key type ${publicKeyFormat}`, + code: 'unsupportedPublicKeyType' + }); } // all of the other did methods so far are signature verification if(!_convertSearchParamToBoolean({param: enableExperimentalPublicKeyTypes})) { @@ -81,8 +84,10 @@ export const validateRequestOptions = ({ 'X25519KeyAgreementKey2020' ]; if(!encryptionFormats.includes(publicKeyFormat)) { - throw new didKeyErrors.InvalidPublicKeyType( - `Invalid Public Key Type ${publicKeyFormat}`); + throw new DidResolverError({ + message: `Invalid Public Key Type ${publicKeyFormat}`, + code: 'invalidPublicKeyType' + }); } // no further checks needed return; @@ -92,8 +97,10 @@ export const validateRequestOptions = ({ 'Ed25519VerificationKey2020' ]; if(!signatureFormats.includes(publicKeyFormat)) { - throw new didKeyErrors.InvalidPublicKeyType( - `Invalid Public Key Type ${publicKeyFormat}`); + throw new DidResolverError({ + message: `Invalid Public Key Type ${publicKeyFormat}`, + code: 'invalidPublicKeyType' + }); } } }; From 6aea527d8b322f7ceeec66eb9e3e0632f1a78b36 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 16:54:05 -0400 Subject: [PATCH 33/55] Add function to turn error codes into statusCodes. --- lib/didResolver.js | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 0fa374a..a4e0d2e 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -57,8 +57,46 @@ export const resolveDid = async ({ } if(error) { return { - statusCode: error.statusCode || 500, + statusCode: _errorToStatusCode({code: error.code}), resolutionResult: new DidResolutionResult({error, isDidUrl: _isDidUrl}) }; } }; + +/** + * Matches an error.code to a status code or 500. + * + * @see https://w3c-ccg.github.io/did-resolution/ + * @private + * + * @param {object} options - Options to use. + * @param {string} options.code - An error. + * + * @returns {number} - An http status code. + */ +function _errorToStatusCode({code}) { + const statuses = [ + {statusCode: 404, codes: ['notFound']}, + { + statusCode: 400, + // FIXME these codes should have more specific statusCodes soon + codes: [ + 'invalidDid', + 'invalidDidUrl', + 'unsupportedPublicKeyType', + // method not supported discussion is here: + // @see https://github.com/w3c-ccg/did-resolution/issues/72 + 'methodNotSupported', + 'invalidPublicKeyType', + 'invalidPublicKey', + 'invalidPublicKeyLength' + ] + }, + {statusCode: 406, codes: ['representationNotSupported']}, + {statusCode: 500, codes: ['internalError']}, + {statusCode: 410, codes: ['deactivated']} + ]; + const {statusCode = 500} = statuses.find( + ({codes}) => codes.includes(code)) || {}; + return statusCode; +} From 23e0f63116414fd05a5e1ea9850e4702d76c6431 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 16:56:12 -0400 Subject: [PATCH 34/55] Remove validators dir & make it a file. --- lib/didResolver.js | 2 +- lib/{validators/index.js => validators.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{validators/index.js => validators.js} (100%) diff --git a/lib/didResolver.js b/lib/didResolver.js index a4e0d2e..e4511f2 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -5,7 +5,7 @@ import {isDidUrl, parseDid} from '@digitalbazaar/did-io'; import { validateDidRequest, validateRequestOptions -} from './validators/index.js'; +} from './validators.js'; import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import logger from './logger.js'; diff --git a/lib/validators/index.js b/lib/validators.js similarity index 100% rename from lib/validators/index.js rename to lib/validators.js From 3364e4a7a044e47eebb31db52e4a69a83d1d19d3 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 17:00:48 -0400 Subject: [PATCH 35/55] Switch route to /1.0/identifiers/resolve/:did. --- lib/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config.js b/lib/config.js index 78c5348..3b96a43 100644 --- a/lib/config.js +++ b/lib/config.js @@ -17,5 +17,5 @@ cfg.publicKeyFormats = [ 'ExperimentalVerificationKey2022' ]; -const basePath = '/1.0/resolve/identifiers/:did'; +const basePath = '/1.0/identifiers/resolve/:did'; cfg.routes = {basePath}; From 2c1c0d56c2281e91bc97498982e254fa8a538a1c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 10:14:50 -0400 Subject: [PATCH 36/55] Improve naming of errorCodeToStatusCode & hasDereferencingMetadata. --- lib/DidResolutionResult.js | 8 ++++---- lib/didResolver.js | 21 +++++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/DidResolutionResult.js b/lib/DidResolutionResult.js index d85aed1..1d34502 100644 --- a/lib/DidResolutionResult.js +++ b/lib/DidResolutionResult.js @@ -6,8 +6,8 @@ * Creates the JSON-LD for the response. * * @param {object} options - Options to use. - * @param {boolean} [options.isDidUrl=false] - Does the response need - * didDereferencingMetadata? + * @param {boolean} [options.hasDereferencingMetadata=false] - Does the + * response need didDereferencingMetadata? * @param {object} [options.didDocument={}] - The didDocument for the * response. * @param {object|null} [options.didDocumentMetadata=null] - Meta data @@ -16,7 +16,7 @@ */ export class DidResolutionResult { constructor({ - isDidUrl = false, + hasDereferencingMetadata = false, didDocument = {}, didDocumentMetadata = null, error @@ -24,7 +24,7 @@ export class DidResolutionResult { this['@context'] = 'https://w3id.org/did-resolution/v1'; this.didDocument = didDocument; this.didDocumentMetadata = didDocumentMetadata; - const metadataProperty = isDidUrl ? + const metadataProperty = hasDereferencingMetadata ? 'didDereferencingMetadata' : 'didResolutionMetadata'; this[metadataProperty] = {}; // only define error if it was passed in diff --git a/lib/didResolver.js b/lib/didResolver.js index e4511f2..a623306 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -29,17 +29,16 @@ export const resolveDid = async ({ publicKeyFormats, didOptions }) => { - let _isDidUrl = false; + let hasDereferencingMetadata = false; let error; try { - _isDidUrl = isDidUrl({did}); + hasDereferencingMetadata = isDidUrl({did}); const {method} = parseDid({did}); await validateDidRequest({method, supportedMethods, didOptions}); const didDocument = await didIo.get({did}); // FIXME didOptions should probably be in didIo // in the future validateRequestOptions({ - did, didDocument, didOptions, publicKeyFormats @@ -48,7 +47,7 @@ export const resolveDid = async ({ statusCode: 200, resolutionResult: new DidResolutionResult({ didDocument, - isDidUrl: _isDidUrl + hasDereferencingMetadata }) }; } catch(e) { @@ -57,8 +56,11 @@ export const resolveDid = async ({ } if(error) { return { - statusCode: _errorToStatusCode({code: error.code}), - resolutionResult: new DidResolutionResult({error, isDidUrl: _isDidUrl}) + statusCode: _errorCodeToStatusCode({code: error.code}), + resolutionResult: new DidResolutionResult({ + error, + hasDereferencingMetadata + }) }; } }; @@ -74,7 +76,7 @@ export const resolveDid = async ({ * * @returns {number} - An http status code. */ -function _errorToStatusCode({code}) { +function _errorCodeToStatusCode({code}) { const statuses = [ {statusCode: 404, codes: ['notFound']}, { @@ -94,7 +96,10 @@ function _errorToStatusCode({code}) { }, {statusCode: 406, codes: ['representationNotSupported']}, {statusCode: 500, codes: ['internalError']}, - {statusCode: 410, codes: ['deactivated']} + // if the did has been deactivated return 410 + {statusCode: 410, codes: ['deactivated']}, + // if the did resolves to a service return 303 + {statusCode: 303, codes: ['service']} ]; const {statusCode = 500} = statuses.find( ({codes}) => codes.includes(code)) || {}; From 99ca72136a1a8040bc255e4aa7cb6452ab21092a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 10:21:21 -0400 Subject: [PATCH 37/55] Move metadata detection into DidResolutionResult. --- lib/DidResolutionResult.js | 7 ++++--- lib/didResolver.js | 14 +++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/DidResolutionResult.js b/lib/DidResolutionResult.js index 1d34502..8c2f2b4 100644 --- a/lib/DidResolutionResult.js +++ b/lib/DidResolutionResult.js @@ -1,13 +1,13 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ +import {isDidUrl} from '@digitalbazaar/did-io'; /** * Creates the JSON-LD for the response. * * @param {object} options - Options to use. - * @param {boolean} [options.hasDereferencingMetadata=false] - Does the - * response need didDereferencingMetadata? + * @param {string} [options.did=''] - The did being resolved. * @param {object} [options.didDocument={}] - The didDocument for the * response. * @param {object|null} [options.didDocumentMetadata=null] - Meta data @@ -16,7 +16,7 @@ */ export class DidResolutionResult { constructor({ - hasDereferencingMetadata = false, + did = '', didDocument = {}, didDocumentMetadata = null, error @@ -24,6 +24,7 @@ export class DidResolutionResult { this['@context'] = 'https://w3id.org/did-resolution/v1'; this.didDocument = didDocument; this.didDocumentMetadata = didDocumentMetadata; + const hasDereferencingMetadata = isDidUrl({did}); const metadataProperty = hasDereferencingMetadata ? 'didDereferencingMetadata' : 'didResolutionMetadata'; this[metadataProperty] = {}; diff --git a/lib/didResolver.js b/lib/didResolver.js index a623306..6c53e7a 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -1,7 +1,6 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import {isDidUrl, parseDid} from '@digitalbazaar/did-io'; import { validateDidRequest, validateRequestOptions @@ -9,6 +8,7 @@ import { import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import logger from './logger.js'; +import {parseDid} from '@digitalbazaar/did-io'; /** * Resolves a did after validating it. @@ -29,10 +29,8 @@ export const resolveDid = async ({ publicKeyFormats, didOptions }) => { - let hasDereferencingMetadata = false; let error; try { - hasDereferencingMetadata = isDidUrl({did}); const {method} = parseDid({did}); await validateDidRequest({method, supportedMethods, didOptions}); const didDocument = await didIo.get({did}); @@ -45,10 +43,7 @@ export const resolveDid = async ({ }); return { statusCode: 200, - resolutionResult: new DidResolutionResult({ - didDocument, - hasDereferencingMetadata - }) + resolutionResult: new DidResolutionResult({did, didDocument}) }; } catch(e) { logger.error(`did resolution error: ${did}`, {error: e}); @@ -57,10 +52,7 @@ export const resolveDid = async ({ if(error) { return { statusCode: _errorCodeToStatusCode({code: error.code}), - resolutionResult: new DidResolutionResult({ - error, - hasDereferencingMetadata - }) + resolutionResult: new DidResolutionResult({did, error}) }; } }; From a05ff0efee3fc8d0c29cc918b1b185023ff39357 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 12:40:48 -0400 Subject: [PATCH 38/55] Remove request options & move to did-method-key. --- lib/config.js | 7 ---- lib/didResolver.js | 56 ++++++++++++++++++++-------- lib/validators.js | 93 ---------------------------------------------- 3 files changed, 40 insertions(+), 116 deletions(-) diff --git a/lib/config.js b/lib/config.js index 3b96a43..e951bb6 100644 --- a/lib/config.js +++ b/lib/config.js @@ -9,13 +9,6 @@ const cfg = config[namespace] = {}; // a list of which did:methods we supported // experimental is for tests only cfg.supportedMethods = ['key', 'v1', 'experimental']; -// FIXME this can be expanded to support Multikey or JsonWebKey2020 -cfg.publicKeyFormats = [ - 'Ed25519VerificationKey2020', - 'X25519KeyAgreementKey2020', - // this is strictly for testing - 'ExperimentalVerificationKey2022' -]; const basePath = '/1.0/identifiers/resolve/:did'; cfg.routes = {basePath}; diff --git a/lib/didResolver.js b/lib/didResolver.js index 6c53e7a..ce63eeb 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -1,14 +1,11 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ -import { - validateDidRequest, - validateRequestOptions -} from './validators.js'; import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import logger from './logger.js'; import {parseDid} from '@digitalbazaar/did-io'; +import {validateDidRequest} from './validators.js'; /** * Resolves a did after validating it. @@ -17,8 +14,6 @@ import {parseDid} from '@digitalbazaar/did-io'; * @param {string} options.did - The did or didUrl being resolved. * @param {Array} options.supportedMethods - A list of * did methods this resolver supports. - * @param {Array} options.publicKeyFormats - A list of - * public key formats this resolver supports. * @param {object} options.didOptions - Options passed to the resolver. * * @returns {object} - Returns a status code and the resolution data. @@ -26,21 +21,15 @@ import {parseDid} from '@digitalbazaar/did-io'; export const resolveDid = async ({ did, supportedMethods, - publicKeyFormats, - didOptions + didOptions: rawOptions }) => { let error; try { const {method} = parseDid({did}); + const didOptions = _convertDidOptions({didOptions: rawOptions}); await validateDidRequest({method, supportedMethods, didOptions}); - const didDocument = await didIo.get({did}); - // FIXME didOptions should probably be in didIo - // in the future - validateRequestOptions({ - didDocument, - didOptions, - publicKeyFormats - }); + const didDocument = await didIo.get({did, options: didOptions}); + // FIXME services are not handled here yet return { statusCode: 200, resolutionResult: new DidResolutionResult({did, didDocument}) @@ -97,3 +86,38 @@ function _errorCodeToStatusCode({code}) { ({codes}) => codes.includes(code)) || {}; return statusCode; } + +// takes in search params / url queries +// and then converts properties that should be booleans +// to booleans +function _convertDidOptions({didOptions}) { + if(!didOptions) { + return; + } + if(didOptions.enableExperimentalPublicKeyTypes) { + didOptions.enableExperimentalPublicKeyTypes = _convertSearchParamToBoolean({ + param: didOptions.enableExperimentalPublicKeyTypes + }); + } + return didOptions; +} + +/** + * Search Params are encoded as strings so this turns + * strings of value '0' or 'false' into false. + * + * @param {object} options - Options to use. + * @param {string} options.param - The param value. + * + * @returns {boolean} Returns a boolean. + */ +function _convertSearchParamToBoolean({param}) { + if(typeof param !== 'string') { + return Boolean(param); + } + const falsey = /0|false/i; + if(falsey.test(param)) { + return false; + } + return true; +} diff --git a/lib/validators.js b/lib/validators.js index f3403b8..1a0101a 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -31,96 +31,3 @@ export const validateDidRequest = ({ }); } }; - -// FIXME while we do validate public key formats we do not -// convert to the requested public key format. -/** - * Public did:keys can be represented in multiple formats. - * While we don't do any conversion in this library we still make - * the check. - * - * @param {object} options - Options to use. - * @param {Array} options.publicKeyFormats - A list of - * public key formats our did:key implementation supports. - * @param {object} options.didOptions - The didOptions from searchParams - * and headers. - * @param {string} options.didOptions.publicKeyFormat - The format - * the public key should be returned in. - * @param {string} options.didOptions.enableExperimentalPublicKeyTypes - An - * option that can be passed in to allow experimental key types. - * @param {object} options.didDocument - The didDocument requred by the did - * or didUrl. - * - * @throws {Error} Throws UnsupportedPublicKeyType or InvalidPublicKeyType. - * - * @returns {undefined} Returns on sucess. - */ -export const validateRequestOptions = ({ - publicKeyFormats = [], - didOptions: { - publicKeyFormat, - enableExperimentalPublicKeyTypes = false - } = {}, - didDocument, -}) => { - // if no publicKeyFormat was in the request just skip this check - if(!publicKeyFormat) { - return; - } - // supported public key formats are set manually on config - if(!publicKeyFormats.includes(publicKeyFormat)) { - throw new DidResolverError({ - message: `Unsupported public key type ${publicKeyFormat}`, - code: 'unsupportedPublicKeyType' - }); - } - // all of the other did methods so far are signature verification - if(!_convertSearchParamToBoolean({param: enableExperimentalPublicKeyTypes})) { - const verificationFormats = ['Multikey', 'JsonWebKey2020']; - //keyAgreement is an encryption verification method - if(didDocument.type === 'X25519KeyAgreementKey2020') { - const encryptionFormats = [ - ...verificationFormats, - 'X25519KeyAgreementKey2020' - ]; - if(!encryptionFormats.includes(publicKeyFormat)) { - throw new DidResolverError({ - message: `Invalid Public Key Type ${publicKeyFormat}`, - code: 'invalidPublicKeyType' - }); - } - // no further checks needed - return; - } - const signatureFormats = [ - ...verificationFormats, - 'Ed25519VerificationKey2020' - ]; - if(!signatureFormats.includes(publicKeyFormat)) { - throw new DidResolverError({ - message: `Invalid Public Key Type ${publicKeyFormat}`, - code: 'invalidPublicKeyType' - }); - } - } -}; - -/** - * Search Params are encoded as strings so this turns - * strings of value '0' or 'false' into false. - * - * @param {object} options - Options to use. - * @param {string} options.param - The param value. - * - * @returns {boolean} Returns a boolean. - */ -function _convertSearchParamToBoolean({param}) { - if(typeof param !== 'string') { - return Boolean(param); - } - const falsey = /0|false/i; - if(falsey.test(param)) { - return false; - } - return true; -} From d9d4794b2b487fbfe33b444d4231935076d7bded Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 13:52:58 -0400 Subject: [PATCH 39/55] Add formatResponse to handle requests that don't want metadata. --- lib/didResolver.js | 62 ++++++++++++++++++++++++++++++++++++---------- lib/http.js | 8 +++--- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index ce63eeb..9f4dfa7 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -15,36 +15,65 @@ import {validateDidRequest} from './validators.js'; * @param {Array} options.supportedMethods - A list of * did methods this resolver supports. * @param {object} options.didOptions - Options passed to the resolver. + * @param {object} options.resolverOptions - Options for the resolver. * * @returns {object} - Returns a status code and the resolution data. */ export const resolveDid = async ({ did, supportedMethods, - didOptions: rawOptions + didOptions: rawOptions, + resolverOptions }) => { let error; + let didDocument; try { const {method} = parseDid({did}); const didOptions = _convertDidOptions({didOptions: rawOptions}); await validateDidRequest({method, supportedMethods, didOptions}); - const didDocument = await didIo.get({did, options: didOptions}); - // FIXME services are not handled here yet - return { - statusCode: 200, - resolutionResult: new DidResolutionResult({did, didDocument}) - }; + didDocument = await didIo.get({did, options: didOptions}); } catch(e) { logger.error(`did resolution error: ${did}`, {error: e}); error = e; } - if(error) { + return _formatResponse({resolverOptions, didDocument, error, did}); +}; + +/** + * Did Resolvers can return the didDocument itself or wrapped with metadata. + * + * @private + * + * @param {object} options - Options to use. + * @param {object} options.resolverOptions - Options for the did resolver + * usually found in headers. + * @param {object} options.didDocument - A didDocument. + * @param {Error} [options.error] - An error. + * @param {string} options.did - The did being resolved or derefernced. + * + * @returns {{statusCode: number, resolutionResult: object}} - What to return + * as the response. + */ +function _formatResponse({resolverOptions, didDocument, error, did}) { + const {accept = ''} = resolverOptions; + const didResolutionContext = 'https://w3id.org/did-resolution'; + const addMetadata = accept.includes(didResolutionContext); + if(!addMetadata) { + if(error) { + throw error; + } return { - statusCode: _errorCodeToStatusCode({code: error.code}), - resolutionResult: new DidResolutionResult({did, error}) + // FIXME services are not handled here yet + statusCode: 200, + resolutionResult: didDocument }; } -}; + return { + //FIXME services should return 303 + statusCode: error ? _errorCodeToStatusCode({code: error.code}) : 200, + resolutionResult: new DidResolutionResult({did, didDocument, error}) + }; +} /** * Matches an error.code to a status code or 500. @@ -115,8 +144,15 @@ function _convertSearchParamToBoolean({param}) { if(typeof param !== 'string') { return Boolean(param); } - const falsey = /0|false/i; - if(falsey.test(param)) { + // the param must start & end with one of these values ignoring case + const booleanParam = /^((?0|false)|(?1|true))$/i; + const {groups} = booleanParam.exec(param.trim()) || {}; + if(!groups) { + throw new Error( + `Expected search param to be 0, "false", 1, or "true" received ${param}`); + } + // if we captured a false value return false + if(groups.false !== undefined) { return false; } return true; diff --git a/lib/http.js b/lib/http.js index f84eccf..6074dbe 100644 --- a/lib/http.js +++ b/lib/http.js @@ -12,7 +12,6 @@ bedrock.events.on('bedrock-express.configure.routes', app => { const { routes, supportedMethods, - publicKeyFormats } = config['bedrock-did-resolver-http']; app.get( routes.basePath, @@ -20,13 +19,14 @@ bedrock.events.on('bedrock-express.configure.routes', app => { const {did} = req.params; // did options are passed to the resolver via url queries // except some are headers on the request - const didOptions = {...req.headers, ...req.query}; + const didOptions = {...req.query}; + const resolverOptions = {...req.headers}; try { const {statusCode, resolutionResult} = await resolveDid({ did, supportedMethods, - publicKeyFormats, - didOptions + didOptions, + resolverOptions }); res.status(statusCode).json(resolutionResult); } catch(error) { From db32c3a46181b1099587d5e8d1b92347449decd8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 17:56:10 -0400 Subject: [PATCH 40/55] Correct test makeRequest basePath. --- lib/didResolver.js | 2 +- test/mocha/helpers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 9f4dfa7..3e90267 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -33,7 +33,7 @@ export const resolveDid = async ({ await validateDidRequest({method, supportedMethods, didOptions}); didDocument = await didIo.get({did, options: didOptions}); } catch(e) { - logger.error(`did resolution error: ${did}`, {error: e}); + logger.error(`DID resolution error: ${did}`, {error: e}); error = e; } return _formatResponse({resolverOptions, didDocument, error, did}); diff --git a/test/mocha/helpers.js b/test/mocha/helpers.js index 8626cad..8f927f3 100644 --- a/test/mocha/helpers.js +++ b/test/mocha/helpers.js @@ -10,7 +10,7 @@ const headers = { }; export const makeRequest = async ({did}) => { - const basePath = '/1.0/resolve/identifiers/'; + const basePath = '/1.0/identifiers/resolve/'; const url = `https://localhost:52443${basePath}${encodeURIComponent(did)}`; const {data} = await httpClient.get(url, {headers, agent}); return data; From 40eed634396b2fb98ecfd0dd78b6274beba789f6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 10:06:28 -0400 Subject: [PATCH 41/55] Sort status codes ascending & add 501 for methodNotSupported. --- lib/didResolver.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 3e90267..a767940 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -88,7 +88,8 @@ function _formatResponse({resolverOptions, didDocument, error, did}) { */ function _errorCodeToStatusCode({code}) { const statuses = [ - {statusCode: 404, codes: ['notFound']}, + // if the did resolves to a service return 303 + {statusCode: 303, codes: ['service']}, { statusCode: 400, // FIXME these codes should have more specific statusCodes soon @@ -96,20 +97,17 @@ function _errorCodeToStatusCode({code}) { 'invalidDid', 'invalidDidUrl', 'unsupportedPublicKeyType', - // method not supported discussion is here: - // @see https://github.com/w3c-ccg/did-resolution/issues/72 - 'methodNotSupported', 'invalidPublicKeyType', 'invalidPublicKey', 'invalidPublicKeyLength' ] }, - {statusCode: 406, codes: ['representationNotSupported']}, - {statusCode: 500, codes: ['internalError']}, + {statusCode: 404, codes: ['notFound']}, // if the did has been deactivated return 410 {statusCode: 410, codes: ['deactivated']}, - // if the did resolves to a service return 303 - {statusCode: 303, codes: ['service']} + {statusCode: 406, codes: ['representationNotSupported']}, + {statusCode: 500, codes: ['internalError']}, + {statusCode: 501, codes: ['methodNotSupported']}, ]; const {statusCode = 500} = statuses.find( ({codes}) => codes.includes(code)) || {}; From 16d594fd1c15bff890459ccdb8a5e4e0b06eac13 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 16:52:58 -0400 Subject: [PATCH 42/55] Remove experimental from supportedMethods. --- lib/config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index e951bb6..2ffe88d 100644 --- a/lib/config.js +++ b/lib/config.js @@ -7,8 +7,7 @@ const namespace = 'bedrock-did-resolver-http'; const cfg = config[namespace] = {}; // a list of which did:methods we supported -// experimental is for tests only -cfg.supportedMethods = ['key', 'v1', 'experimental']; +cfg.supportedMethods = ['key', 'v1']; const basePath = '/1.0/identifiers/resolve/:did'; cfg.routes = {basePath}; From 8b06c200f9c0d3958eda159f2e0d8a037d727af6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 16:59:37 -0400 Subject: [PATCH 43/55] Make supported methods a Set. --- lib/config.js | 2 +- lib/validators.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index 2ffe88d..b8d79d1 100644 --- a/lib/config.js +++ b/lib/config.js @@ -7,7 +7,7 @@ const namespace = 'bedrock-did-resolver-http'; const cfg = config[namespace] = {}; // a list of which did:methods we supported -cfg.supportedMethods = ['key', 'v1']; +cfg.supportedMethods = new Set(['key', 'v1']); const basePath = '/1.0/identifiers/resolve/:did'; cfg.routes = {basePath}; diff --git a/lib/validators.js b/lib/validators.js index 1a0101a..b1cf9a8 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -24,7 +24,7 @@ export const validateDidRequest = ({ supportedMethods, }) => { // this is for conformance with the did resolver spec - if(!supportedMethods.includes(method)) { + if(!supportedMethods.has(method)) { throw new DidResolverError({ message: `Unsupported method ${method}`, code: 'methodNotSupported' From d88e2de828d071eea78e1792e5526bfa481955c1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 11 Aug 2022 09:38:58 -0400 Subject: [PATCH 44/55] Remove logic for supportedMethods. --- lib/config.js | 3 --- lib/didResolver.js | 7 ------- lib/validators.js | 33 --------------------------------- 3 files changed, 43 deletions(-) delete mode 100644 lib/validators.js diff --git a/lib/config.js b/lib/config.js index b8d79d1..0a78e03 100644 --- a/lib/config.js +++ b/lib/config.js @@ -6,8 +6,5 @@ import {config} from '@bedrock/core'; const namespace = 'bedrock-did-resolver-http'; const cfg = config[namespace] = {}; -// a list of which did:methods we supported -cfg.supportedMethods = new Set(['key', 'v1']); - const basePath = '/1.0/identifiers/resolve/:did'; cfg.routes = {basePath}; diff --git a/lib/didResolver.js b/lib/didResolver.js index a767940..7ac41ee 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -4,16 +4,12 @@ import {didIo} from '@bedrock/did-io'; import {DidResolutionResult} from './DidResolutionResult.js'; import logger from './logger.js'; -import {parseDid} from '@digitalbazaar/did-io'; -import {validateDidRequest} from './validators.js'; /** * Resolves a did after validating it. * * @param {object} options - Options to use. * @param {string} options.did - The did or didUrl being resolved. - * @param {Array} options.supportedMethods - A list of - * did methods this resolver supports. * @param {object} options.didOptions - Options passed to the resolver. * @param {object} options.resolverOptions - Options for the resolver. * @@ -21,16 +17,13 @@ import {validateDidRequest} from './validators.js'; */ export const resolveDid = async ({ did, - supportedMethods, didOptions: rawOptions, resolverOptions }) => { let error; let didDocument; try { - const {method} = parseDid({did}); const didOptions = _convertDidOptions({didOptions: rawOptions}); - await validateDidRequest({method, supportedMethods, didOptions}); didDocument = await didIo.get({did, options: didOptions}); } catch(e) { logger.error(`DID resolution error: ${did}`, {error: e}); diff --git a/lib/validators.js b/lib/validators.js deleted file mode 100644 index b1cf9a8..0000000 --- a/lib/validators.js +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. - */ - -import {DidResolverError} from '@digitalbazaar/did-io'; - -/** - * Validates the components of a did. - * - * @param {object} options - Options to use. - * @param {string} options.method - The did method of the did - * being resolved. - * @param {Array} options.supportedMethods - A list of - * did:key methods this implementation supports. - * - * @throws {Error} Throws did related errors. - * - * @returns {boolean} - Are the did components valid? - */ -// fixme this should probably be moved into the did-method-key -// get function and be run before returning a didDocument -export const validateDidRequest = ({ - method, - supportedMethods, -}) => { - // this is for conformance with the did resolver spec - if(!supportedMethods.has(method)) { - throw new DidResolverError({ - message: `Unsupported method ${method}`, - code: 'methodNotSupported' - }); - } -}; From 81a8d709dff62c90ce45c6d34ba0c617543ead9f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 13:43:43 -0400 Subject: [PATCH 45/55] Use node 16-18 in ci actions. --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e3a6242..d497a2c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [14.x] + node-version: [16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} @@ -29,7 +29,7 @@ jobs: - 27017:27017 strategy: matrix: - node-version: [14.x, 16.x] + node-version: [16.x, 18.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} @@ -55,7 +55,7 @@ jobs: - 27017:27017 strategy: matrix: - node-version: [14.x] + node-version: [16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} From 337f2ffcbf0c142cd75d8ef2615599e76d3cbaf1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:21:39 -0400 Subject: [PATCH 46/55] Remove unneeded line from https.js. Co-authored-by: Tashi D. Gyeltshen <38434620+JSAssassin@users.noreply.github.com> --- lib/http.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 6074dbe..3524f2a 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1,7 +1,6 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ - import * as bedrock from '@bedrock/core'; import {asyncHandler} from '@bedrock/express'; import logger from './logger.js'; From 4f16b24c90b60d3d0053a4986b25cc28f60f836e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:22:01 -0400 Subject: [PATCH 47/55] Remove 'use strict' from logger.js. Co-authored-by: Tashi D. Gyeltshen <38434620+JSAssassin@users.noreply.github.com> --- lib/logger.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/logger.js b/lib/logger.js index e921632..eed81e0 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -2,7 +2,6 @@ /*! * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. */ - import * as bedrock from '@bedrock/core'; const logger = bedrock.loggers.get('app').child('bedrock-did-resolver-http'); From b25d60976e76de37c4aa45b720ac5260377a0475 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:23:15 -0400 Subject: [PATCH 48/55] Remove extra line in helpers.js. Co-authored-by: Tashi D. Gyeltshen <38434620+JSAssassin@users.noreply.github.com> --- test/mocha/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mocha/helpers.js b/test/mocha/helpers.js index 8f927f3..2036641 100644 --- a/test/mocha/helpers.js +++ b/test/mocha/helpers.js @@ -1,7 +1,6 @@ /* * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ - import {agent} from '@bedrock/https-agent'; import {httpClient} from '@digitalbazaar/http-client'; From 297950b43da4648a6b4d2bd0b84edc951b080df8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:23:42 -0400 Subject: [PATCH 49/55] Remove 'use strict' from test.config.js. Co-authored-by: Tashi D. Gyeltshen <38434620+JSAssassin@users.noreply.github.com> --- test/test.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test.config.js b/test/test.config.js index 6755195..f535e8f 100644 --- a/test/test.config.js +++ b/test/test.config.js @@ -1,7 +1,6 @@ /* * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ - import {config} from '@bedrock/core'; import {fileURLToPath} from 'node:url'; import path from 'path'; From 06560a3b01064b4ca677e754ababe80897dc2248 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:24:03 -0400 Subject: [PATCH 50/55] Remove extra line from test.js. Co-authored-by: Tashi D. Gyeltshen <38434620+JSAssassin@users.noreply.github.com> --- test/test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test.js b/test/test.js index 065b8aa..ca0310e 100644 --- a/test/test.js +++ b/test/test.js @@ -1,7 +1,6 @@ /* * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ - import * as bedrock from '@bedrock/core'; import '@bedrock/https-agent'; import '@bedrock/mongodb'; From e83a8ce5cadc37d62f1dc8590dafc7db4a50f8bf Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:28:42 -0400 Subject: [PATCH 51/55] Better document did and resolver options. --- lib/didResolver.js | 6 ++++-- lib/http.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 7ac41ee..02f523a 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -10,8 +10,10 @@ import logger from './logger.js'; * * @param {object} options - Options to use. * @param {string} options.did - The did or didUrl being resolved. - * @param {object} options.didOptions - Options passed to the resolver. - * @param {object} options.resolverOptions - Options for the resolver. + * @param {object} options.didOptions - Options passed to be passed to the did + * method driver. + * @param {object} options.resolverOptions - Options for the resolver such as + * the format of the response data. * * @returns {object} - Returns a status code and the resolution data. */ diff --git a/lib/http.js b/lib/http.js index 3524f2a..a3acb96 100644 --- a/lib/http.js +++ b/lib/http.js @@ -17,8 +17,8 @@ bedrock.events.on('bedrock-express.configure.routes', app => { asyncHandler(async (req, res) => { const {did} = req.params; // did options are passed to the resolver via url queries - // except some are headers on the request const didOptions = {...req.query}; + // the accept header tells the resolver how to format the response data const resolverOptions = {...req.headers}; try { const {statusCode, resolutionResult} = await resolveDid({ From 5e918ab1d3a250ff491ddf658584d5cb314960e5 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 15 Aug 2022 11:30:15 -0400 Subject: [PATCH 52/55] Add @db/http-client to test project. --- test/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/package.json b/test/package.json index ee3d0d0..093d4ee 100644 --- a/test/package.json +++ b/test/package.json @@ -16,6 +16,7 @@ "@bedrock/mongodb": "^10.0.0", "@bedrock/server": "^5.0.0", "@bedrock/test": "^8.0.0", + "@digitalbazaar/http-client": "^3.2.0", "bedrock-did-resolver-http": "file:..", "c8": "^7.12.0", "cross-env": "^7.0.3" From c4ff86b2ce2d67ac83411c7e283159bc24461398 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 23 Aug 2022 10:33:14 -0400 Subject: [PATCH 53/55] Change nyc opts to c8 opts. --- test/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/package.json b/test/package.json index 093d4ee..0e6ac72 100644 --- a/test/package.json +++ b/test/package.json @@ -21,7 +21,7 @@ "c8": "^7.12.0", "cross-env": "^7.0.3" }, - "nyc": { + "c8": { "excludeNodeModules": false, "include": [ "node_modules/bedrock-did-resolver-http/**" From cae41cc8ce0adf1a7138820e3ac73b37057d934b Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 23 Aug 2022 12:09:30 -0400 Subject: [PATCH 54/55] Remove assertNoError from lint test globals. --- test/mocha/.eslintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mocha/.eslintrc b/test/mocha/.eslintrc index f9f5022..6f4ff4e 100644 --- a/test/mocha/.eslintrc +++ b/test/mocha/.eslintrc @@ -3,7 +3,6 @@ "mocha": true }, "globals": { - "assertNoError": true, "should": true } } From 809aaa354ceeccbb75cf2035360c061cfd67022e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 29 Dec 2022 16:49:17 -0500 Subject: [PATCH 55/55] Correct typo in jsdoc string for didOptions. Co-authored-by: Tashi D. Gyeltshen <38434620+JSAssassin@users.noreply.github.com> --- lib/didResolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/didResolver.js b/lib/didResolver.js index 02f523a..f85b419 100644 --- a/lib/didResolver.js +++ b/lib/didResolver.js @@ -10,7 +10,7 @@ import logger from './logger.js'; * * @param {object} options - Options to use. * @param {string} options.did - The did or didUrl being resolved. - * @param {object} options.didOptions - Options passed to be passed to the did + * @param {object} options.didOptions - Options to be passed to the did * method driver. * @param {object} options.resolverOptions - Options for the resolver such as * the format of the response data.