-
Notifications
You must be signed in to change notification settings - Fork 6
Add did key spec 7 validators #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3323fbe
a0d8322
c833a50
3128d6a
e037690
93a4df4
f4bff2e
b811080
d2de11b
98135e8
e418733
6287e70
82812a7
5e5dc3e
7a76eae
99a3618
349129f
6f9062b
93aa105
dee6f49
c580add
b9a0aa2
79bbbad
150cd6f
e465e3b
13a9c6c
2237b54
8c93c99
debba55
adb593d
d722b55
402a14d
389fa2c
720fe4f
f1f6f69
3486bd2
d976086
e77d4b7
50ae168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /*! | ||
| * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
|
|
||
| /** | ||
| * Error for throwing did syntax related errors. | ||
| * | ||
| * @param {object} options - Options to use. | ||
| * @param {string} options.message - An error message. | ||
| * @param {string} options.code - A DID core error. | ||
| * @param {object} options.params - Params to be passed to the base Error Class. | ||
| * | ||
| */ | ||
| export class DidResolverError extends Error { | ||
| constructor({message, code, params}) { | ||
| super(message, params); | ||
| this.name = 'DidResolverError'; | ||
| this.code = code; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -194,22 +194,34 @@ export function _methodById({doc, methodId}) { | |
| } | ||
|
|
||
| /** | ||
| * Parses the DID into various component (currently, only cares about prefix). | ||
| * Parses the DID into scheme and method components. | ||
| * | ||
| * @example | ||
| * parseDid({did: 'did:v1:test:nym'}); | ||
| * // -> {prefix: 'v1'} | ||
| * // -> {scheme: 'did', method: 'v1'} | ||
| * | ||
| * @param {string} did - DID uri. | ||
| * | ||
| * @returns {{prefix: string}} Returns the method prefix (without `did:`). | ||
| * @returns {{scheme: string, method: string}} Returns the scheme and method. | ||
| */ | ||
| export function parseDid({did}) { | ||
| if(!did) { | ||
| throw new TypeError('DID cannot be empty.'); | ||
| } | ||
|
|
||
| const prefix = did.split(':').slice(1, 2).join(':'); | ||
| const [scheme, method] = did.split(':'); | ||
|
|
||
| return {prefix}; | ||
| return {scheme, method}; | ||
| } | ||
|
|
||
| /** | ||
| * Determines if a DID has URL characters in it. | ||
| * | ||
| * @param {object} options - Options to use. | ||
| * @param {string} options.did - A DID. | ||
| * | ||
| * @returns {boolean} Does the DID potentially have a query or a fragment in it? | ||
| */ | ||
| export function isDidUrl({did}) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This naming is a bit confusing at first glance. I assume this is trying to check between DID Syntax and DID URL Syntax? It looks like the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All DIDs are valid DID URLs, but not all DID URLS are valid DIDs. A DID URL can for instance end with |
||
| return /[\/#?]/.test(did); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /*! | ||
| * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| import {isDidUrl} from './did-io.js'; | ||
| import {DidResolverError} from './DidResolverError.js'; | ||
|
|
||
| /** | ||
| * Determines if a DID contains URL characters and | ||
| * then validates either the DID URL or DID. | ||
| * | ||
| * @param {object} options - Options to use. | ||
| * @param {string} options.did - A DID. | ||
| * | ||
| * @throws {DidResolverError} Throws if DID is invalid. | ||
| * | ||
| * @returns {undefined} Returns on success. | ||
| */ | ||
| export function validateDid({did}) { | ||
| if(!did) { | ||
| throw new TypeError('The parameter "did" is required.'); | ||
| } | ||
| if(isDidUrl({did})) { | ||
| if(!isValidDidUrl({didUrl: did})) { | ||
| throw new DidResolverError({ | ||
| message: `Invalid DID URL "${did}"`, | ||
| code: 'invalidDidUrl' | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
| if(!isValidDid({did})) { | ||
| throw new DidResolverError({ | ||
| message: `Invalid DID "${did}"`, | ||
| code: 'invalidDid' | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Validates a DID, but not a DID URL. | ||
| * This function comes from the did-test-suite. | ||
davidlehn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * @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 if DID is valid. | ||
| */ | ||
| export function isValidDid({did}) { | ||
| const didRegex1 = new RegExp('^did:(?<method_name>[a-z0-9]+):' + | ||
| '(?<method_specific_id>([a-zA-Z0-9\\.\\-_]|%[0-9a-fA-F]{2}|:)+$)'); | ||
| const didRegex2 = /:$/; | ||
| return didRegex1.test(did) && !didRegex2.test(did); | ||
| } | ||
|
|
||
| /** | ||
| * Validates a DID URL including the fragment and queries. | ||
| * This function comes from the did-test-suite. | ||
davidlehn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * @see https://github.com/w3c/did-test-suite/ | ||
| * | ||
| * @param {object} options - Options to use. | ||
| * @param {string} options.didUrl - A prospective DID URL. | ||
| * | ||
| * @returns {boolean} - Returns true or false. | ||
| */ | ||
| export 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); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| /*! | ||
| * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. | ||
| */ | ||
| import chai from 'chai'; | ||
| import {testDid} from './helpers.js'; | ||
| import {isValidDid, isValidDidUrl} from '../lib/validators.js'; | ||
| import {DidResolverError} from '../lib/DidResolverError.js'; | ||
| import { | ||
| typeErrors, | ||
| invalidDids, | ||
| invalidDidSyntax, | ||
| invalidDidUrlSyntax, | ||
| invalidDidUrls, | ||
| validDids, | ||
| validDidUrls, | ||
| } from './mock.data.js'; | ||
|
|
||
| const should = chai.should(); | ||
|
|
||
| describe('validateDid', () => { | ||
| describe('should not throw', () => { | ||
| const inputs = new Set([...validDids, ...validDidUrls]); | ||
| for(const input of inputs) { | ||
| it(`should validate ${input}`, async () => { | ||
| const error = testDid(input); | ||
| should.not.exist(error, `Expected no error for did ${input}`); | ||
| }); | ||
| } | ||
| }); | ||
| describe('should throw `TypeError`', () => { | ||
| for(const input of typeErrors) { | ||
| it(`should not validate ${input}`, async () => { | ||
| const error = testDid(input); | ||
| should.exist(error, `Expected error for did ${input}`); | ||
| error.should.be.instanceof( | ||
| TypeError, | ||
| `Expected a TypeError for ${input}` | ||
| ); | ||
| }); | ||
| } | ||
| }); | ||
| describe('should throw `invalidDid`', () => { | ||
| const inputs = [...invalidDidSyntax, 'did:key:z4345345:']; | ||
| for(const input of inputs) { | ||
| it(`should not validate ${input}`, async () => { | ||
| const error = testDid(input); | ||
| should.exist(error, `Expected error for did ${input}`); | ||
| error.should.be.instanceof( | ||
| DidResolverError, | ||
| `Expected a DidResolverError for ${input}` | ||
| ); | ||
| error.code.should.equal( | ||
| 'invalidDid', | ||
| `Expected ${input} to be an invalid did.` | ||
| ); | ||
| }); | ||
| } | ||
| }); | ||
| describe('should throw `invalidDidUrl`', () => { | ||
| for(const invalidDidUrl of invalidDidUrlSyntax) { | ||
| it(`should not validate ${invalidDidUrl}`, async () => { | ||
| const error = testDid(invalidDidUrl); | ||
| should.exist(error, `Expected error for did url ${invalidDidUrl}`); | ||
| error.should.be.instanceof( | ||
| DidResolverError, | ||
| `Expected a DidResolverError for ${invalidDidUrl}` | ||
| ); | ||
| error.code.should.equal( | ||
| 'invalidDidUrl', | ||
| `Expected ${invalidDidUrl} to be an invalid did url` | ||
| ); | ||
| }); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('isValidDidUrl', () => { | ||
| for(const validDidUrl of validDidUrls) { | ||
| it(`should validate ${validDidUrl}`, async () => { | ||
| const result = isValidDidUrl({didUrl: validDidUrl}); | ||
| should.exist(result, `Expected result for ${validDidUrl} to exist.`); | ||
| result.should.be.a( | ||
| 'boolean', 'Expected isValidDidUrl to return a boolean'); | ||
| result.should.equal(true, `Expected ${validDidUrl} to validate`); | ||
| }); | ||
| } | ||
| for(const invalidDidUrl of invalidDidUrls) { | ||
| it(`should not validate ${invalidDidUrl}`, async () => { | ||
| const result = isValidDidUrl({didUrl: invalidDidUrl}); | ||
| should.exist(result, `Expected result for ${invalidDidUrl} to exist.`); | ||
| result.should.be.a( | ||
| 'boolean', 'Expected isValidDidUrl to return a boolean'); | ||
| result.should.equal(false, `Expected ${invalidDidUrl} to not validate`); | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| describe('isValidDid', () => { | ||
| for(const validDid of validDids) { | ||
| it(`should validate ${validDid}`, async () => { | ||
| const result = isValidDid({did: validDid}); | ||
| should.exist(result, `Expected result for ${validDid} to exist.`); | ||
| result.should.be.a('boolean', 'Expected isValidDid to return a boolean'); | ||
| result.should.equal(true, `Expected ${validDid} to validate`); | ||
| }); | ||
| } | ||
| for(const invalidDid of invalidDids) { | ||
| it(`should not validate ${invalidDid}`, async () => { | ||
| const result = isValidDid({did: invalidDid}); | ||
| should.exist(result, `Expected result for ${invalidDid} to exist.`); | ||
| result.should.be.a('boolean', 'Expected isValidDid to return a boolean'); | ||
| result.should.equal(false, `Expected ${invalidDid} to not validate`); | ||
| }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this change to an arguments object like
{driver: d, method: m}?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've never used that pattern for registering plugins or middleware (I forget what the name of this pattern is) so I just stuck with what we already have. I might add that method parameter exists for one reason: test data this might be over thinking an optional parameter.