@digitalbazaar/vc
Advanced tools
Comparing version 2.1.0 to 3.0.0
/*! | ||
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved. | ||
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
'use strict'; | ||
import { | ||
contexts as credentialContexts | ||
} from 'credentials-context'; | ||
import { | ||
CONTEXT as vcExamplesV1Context, | ||
CONTEXT_URL as vcExamplesV1ContextUrl | ||
} from './vc-examples-v1.js'; | ||
import { | ||
CONTEXT as odrlContext, | ||
CONTEXT_URL as odrlContextUrl | ||
} from './odrl.js'; | ||
const {contexts, constants: contextConstants} = require('credentials-context'); | ||
export const contexts = {}; | ||
const exportedContexts = module.exports = { | ||
'https://www.w3.org/2018/credentials/examples/v1': | ||
require('./vc-examples-v1'), | ||
'https://www.w3.org/ns/odrl.jsonld': require('./odrl') | ||
}; | ||
contexts[vcExamplesV1ContextUrl] = vcExamplesV1Context; | ||
contexts[odrlContextUrl] = odrlContext; | ||
for(const c in contextConstants) { | ||
const contextUrl = contextConstants[c]; | ||
exportedContexts[contextUrl] = contexts.get(contextUrl); | ||
for(const [url, context] of credentialContexts.entries()) { | ||
contexts[url] = context; | ||
} |
/*! | ||
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved. | ||
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
'use strict'; | ||
export const CONTEXT_URL = 'https://www.w3.org/ns/odrl.jsonld'; | ||
/* eslint-disable quote-props, key-spacing, max-len */ | ||
module.exports = { | ||
export const CONTEXT = { | ||
'@context': { | ||
@@ -9,0 +9,0 @@ 'odrl': 'http://www.w3.org/ns/odrl/2/', |
/*! | ||
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved. | ||
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
export const CONTEXT_URL = 'https://www.w3.org/2018/credentials/examples/v1'; | ||
/* eslint-disable quote-props */ | ||
module.exports = { | ||
export const CONTEXT = { | ||
'@context': [ | ||
@@ -8,0 +9,0 @@ { |
/*! | ||
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved. | ||
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
'use strict'; | ||
const jsonld = require('jsonld'); | ||
const {AssertionProofPurpose} = require('jsonld-signatures').purposes; | ||
import jsonld from 'jsonld'; | ||
import jsigs from 'jsonld-signatures'; | ||
const {purposes: {AssertionProofPurpose}} = jsigs; | ||
/** | ||
@@ -13,3 +14,3 @@ * Creates a proof purpose that will validate whether or not the verification | ||
*/ | ||
class CredentialIssuancePurpose extends AssertionProofPurpose { | ||
export class CredentialIssuancePurpose extends AssertionProofPurpose { | ||
/** | ||
@@ -81,3 +82,1 @@ * @param {object} options - The options to use. | ||
} | ||
module.exports = CredentialIssuancePurpose; |
/*! | ||
* Copyright (c) 2019-2021 Digital Bazaar, Inc. All rights reserved. | ||
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
'use strict'; | ||
// load locally embedded contexts | ||
const contexts = require('./contexts'); | ||
import {contexts} from './contexts/index.js'; | ||
module.exports = async function documentLoader(url) { | ||
export async function documentLoader(url) { | ||
const context = contexts[url]; | ||
@@ -19,2 +17,2 @@ if(context !== undefined) { | ||
throw new Error(`Document loader unable to load URL "${url}".`); | ||
}; | ||
} |
618
lib/index.js
/** | ||
* A library for working with verifiable credentials - vc library. | ||
* A JavaScript implementation of Verifiable Credentials. | ||
* | ||
* @author Dave Longley | ||
* @author David I. Lehn | ||
* | ||
* Copyright 2017-2021 Digital Bazaar, Inc. | ||
* @license BSD 3-Clause License | ||
* Copyright (c) 2017-2022 Digital Bazaar, Inc. | ||
* All rights reserved. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* | ||
* Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* | ||
* Redistributions in binary form must reproduce the above copyright | ||
* notice, this list of conditions and the following disclaimer in the | ||
* documentation and/or other materials provided with the distribution. | ||
* | ||
* Neither the name of the Digital Bazaar, Inc. nor the names of its | ||
* contributors may be used to endorse or promote products derived from | ||
* this software without specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS | ||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | ||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A | ||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
module.exports = require('./vc.js'); | ||
import jsonld from 'jsonld'; | ||
import jsigs from 'jsonld-signatures'; | ||
import {CredentialIssuancePurpose} from './CredentialIssuancePurpose.js'; | ||
import {documentLoader as _documentLoader} from './documentLoader.js'; | ||
export const defaultDocumentLoader = | ||
jsigs.extendContextLoader(_documentLoader); | ||
import * as credentialsContext from 'credentials-context'; | ||
const {AuthenticationProofPurpose} = jsigs.purposes; | ||
const {constants: {CREDENTIALS_CONTEXT_V1_URL}} = credentialsContext; | ||
export {CredentialIssuancePurpose}; | ||
// Z and T can be lowercase | ||
// RFC3339 regex | ||
export const dateRegex = new RegExp('^(\\d{4})-(0[1-9]|1[0-2])-' + | ||
'(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):' + | ||
'([0-5][0-9]):([0-5][0-9]|60)' + | ||
'(\\.[0-9]+)?(Z|(\\+|-)([01][0-9]|2[0-3]):' + | ||
'([0-5][0-9]))$', 'i'); | ||
/** | ||
* @typedef {object} LinkedDataSignature | ||
*/ | ||
/** | ||
* @typedef {object} Presentation | ||
*/ | ||
/** | ||
* @typedef {object} ProofPurpose | ||
*/ | ||
/** | ||
* @typedef {object} VerifiableCredential | ||
*/ | ||
/** | ||
* @typedef {object} VerifiablePresentation | ||
*/ | ||
/** | ||
* @typedef {object} VerifyPresentationResult | ||
* @property {boolean} verified - True if verified, false if not. | ||
* @property {object} presentationResult | ||
* @property {Array} credentialResults | ||
* @property {object} error | ||
*/ | ||
/** | ||
* @typedef {object} VerifyCredentialResult | ||
* @property {boolean} verified - True if verified, false if not. | ||
* @property {object} statusResult | ||
* @property {Array} results | ||
* @property {object} error | ||
*/ | ||
/** | ||
* Issues a verifiable credential (by taking a base credential document, | ||
* and adding a digital signature to it). | ||
* | ||
* @param {object} [options={}] - The options to use. | ||
* | ||
* @param {object} options.credential - Base credential document. | ||
* @param {LinkedDataSignature} options.suite - Signature suite (with private | ||
* key material), passed in to sign(). | ||
* | ||
* @param {ProofPurpose} [options.purpose] - A ProofPurpose. If not specified, | ||
* a default purpose will be created. | ||
* | ||
* Other optional params passed to `sign()`: | ||
* @param {object} [options.documentLoader] - A document loader. | ||
* @param {object} [options.expansionMap] - An expansion map. | ||
* | ||
* @throws {Error} If missing required properties. | ||
* | ||
* @returns {Promise<VerifiableCredential>} Resolves on completion. | ||
*/ | ||
export async function issue({ | ||
credential, suite, expansionMap, | ||
purpose = new CredentialIssuancePurpose(), | ||
documentLoader = defaultDocumentLoader | ||
} = {}) { | ||
// check to make sure the `suite` has required params | ||
// Note: verificationMethod defaults to publicKey.id, in suite constructor | ||
if(!suite) { | ||
throw new TypeError('"suite" parameter is required for issuing.'); | ||
} | ||
if(!suite.verificationMethod) { | ||
throw new TypeError('"suite.verificationMethod" property is required.'); | ||
} | ||
if(!credential) { | ||
throw new TypeError('"credential" parameter is required for issuing.'); | ||
} | ||
// Set the issuance date to now(), if missing | ||
if(!credential.issuanceDate) { | ||
const now = (new Date()).toJSON(); | ||
credential.issuanceDate = `${now.substr(0, now.length - 5)}Z`; | ||
} | ||
// run common credential checks | ||
_checkCredential(credential); | ||
return jsigs.sign(credential, {purpose, documentLoader, suite, expansionMap}); | ||
} | ||
/** | ||
* Verifies a verifiable presentation: | ||
* - Checks that the presentation is well-formed | ||
* - Checks the proofs (for example, checks digital signatures against the | ||
* provided public keys). | ||
* | ||
* @param {object} [options={}] - The options to use. | ||
* | ||
* @param {VerifiablePresentation} options.presentation - Verifiable | ||
* presentation, signed or unsigned, that may contain within it a | ||
* verifiable credential. | ||
* | ||
* @param {LinkedDataSignature|LinkedDataSignature[]} options.suite - One or | ||
* more signature suites that are supported by the caller's use case. This is | ||
* an explicit design decision -- the calling code must specify which | ||
* signature types (ed25519, RSA, etc) are allowed. | ||
* Although it is expected that the secure resolution/fetching of the public | ||
* key material (to verify against) is to be handled by the documentLoader, | ||
* the suite param can optionally include the key directly. | ||
* | ||
* @param {boolean} [options.unsignedPresentation=false] - By default, this | ||
* function assumes that a presentation is signed (and will return an error if | ||
* a `proof` section is missing). Set this to `true` if you're using an | ||
* unsigned presentation. | ||
* | ||
* Either pass in a proof purpose, | ||
* @param {AuthenticationProofPurpose} [options.presentationPurpose] - Optional | ||
* proof purpose (a default one will be created if not passed in). | ||
* | ||
* or a default purpose will be created with params: | ||
* @param {string} [options.challenge] - Required if purpose is not passed in. | ||
* @param {string} [options.controller] - A controller. | ||
* @param {string} [options.domain] - A domain. | ||
* | ||
* @param {Function} [options.documentLoader] - A document loader. | ||
* @param {Function} [options.checkStatus] - Optional function for checking | ||
* credential status if `credentialStatus` is present on the credential. | ||
* | ||
* @returns {Promise<VerifyPresentationResult>} The verification result. | ||
*/ | ||
export async function verify(options = {}) { | ||
const {presentation} = options; | ||
try { | ||
if(!presentation) { | ||
throw new TypeError( | ||
'A "presentation" property is required for verifying.'); | ||
} | ||
return _verifyPresentation(options); | ||
} catch(error) { | ||
return { | ||
verified: false, | ||
results: [{presentation, verified: false, error}], | ||
error | ||
}; | ||
} | ||
} | ||
/** | ||
* Verifies a verifiable credential: | ||
* - Checks that the credential is well-formed | ||
* - Checks the proofs (for example, checks digital signatures against the | ||
* provided public keys). | ||
* | ||
* @param {object} [options={}] - The options. | ||
* | ||
* @param {object} options.credential - Verifiable credential. | ||
* | ||
* @param {LinkedDataSignature|LinkedDataSignature[]} options.suite - One or | ||
* more signature suites that are supported by the caller's use case. This is | ||
* an explicit design decision -- the calling code must specify which | ||
* signature types (ed25519, RSA, etc) are allowed. | ||
* Although it is expected that the secure resolution/fetching of the public | ||
* key material (to verify against) is to be handled by the documentLoader, | ||
* the suite param can optionally include the key directly. | ||
* | ||
* @param {CredentialIssuancePurpose} [options.purpose] - Optional | ||
* proof purpose (a default one will be created if not passed in). | ||
* @param {Function} [options.documentLoader] - A document loader. | ||
* @param {Function} [options.checkStatus] - Optional function for checking | ||
* credential status if `credentialStatus` is present on the credential. | ||
* | ||
* @returns {Promise<VerifyCredentialResult>} The verification result. | ||
*/ | ||
export async function verifyCredential(options = {}) { | ||
const {credential} = options; | ||
try { | ||
if(!credential) { | ||
throw new TypeError( | ||
'A "credential" property is required for verifying.'); | ||
} | ||
return await _verifyCredential(options); | ||
} catch(error) { | ||
return { | ||
verified: false, | ||
results: [{credential, verified: false, error}], | ||
error | ||
}; | ||
} | ||
} | ||
/** | ||
* Verifies a verifiable credential. | ||
* | ||
* @private | ||
* @param {object} [options={}] - The options. | ||
* | ||
* @param {object} options.credential - Verifiable credential. | ||
* @param {LinkedDataSignature|LinkedDataSignature[]} options.suite - See the | ||
* definition in the `verify()` docstring, for this param. | ||
* | ||
* @throws {Error} If required parameters are missing (in `_checkCredential`). | ||
* | ||
* @param {CredentialIssuancePurpose} [options.purpose] - A purpose. | ||
* @param {Function} [options.documentLoader] - A document loader. | ||
* @param {Function} [options.checkStatus] - Optional function for checking | ||
* credential status if `credentialStatus` is present on the credential. | ||
* | ||
* @returns {Promise<VerifyCredentialResult>} The verification result. | ||
*/ | ||
async function _verifyCredential(options = {}) { | ||
const {credential, checkStatus} = options; | ||
// run common credential checks | ||
_checkCredential(credential); | ||
// if credential status is provided, a `checkStatus` function must be given | ||
if(credential.credentialStatus && typeof options.checkStatus !== 'function') { | ||
throw new TypeError( | ||
'A "checkStatus" function must be given to verify credentials with ' + | ||
'"credentialStatus".'); | ||
} | ||
const documentLoader = options.documentLoader || defaultDocumentLoader; | ||
const {controller} = options; | ||
const purpose = options.purpose || new CredentialIssuancePurpose({ | ||
controller | ||
}); | ||
const result = await jsigs.verify( | ||
credential, {purpose, documentLoader, ...options}); | ||
// if verification has already failed, skip status check | ||
if(!result.verified) { | ||
return result; | ||
} | ||
if(credential.credentialStatus) { | ||
result.statusResult = await checkStatus(options); | ||
if(!result.statusResult.verified) { | ||
result.verified = false; | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Creates an unsigned presentation from a given verifiable credential. | ||
* | ||
* @param {object} options - Options to use. | ||
* @param {object|Array<object>} [options.verifiableCredential] - One or more | ||
* verifiable credential. | ||
* @param {string} [options.id] - Optional VP id. | ||
* @param {string} [options.holder] - Optional presentation holder url. | ||
* | ||
* @throws {TypeError} If verifiableCredential param is missing. | ||
* @throws {Error} If the credential (or the presentation params) are missing | ||
* required properties. | ||
* | ||
* @returns {Presentation} The credential wrapped inside of a | ||
* VerifiablePresentation. | ||
*/ | ||
export function createPresentation({verifiableCredential, id, holder} = {}) { | ||
const presentation = { | ||
'@context': [CREDENTIALS_CONTEXT_V1_URL], | ||
type: ['VerifiablePresentation'] | ||
}; | ||
if(verifiableCredential) { | ||
const credentials = [].concat(verifiableCredential); | ||
// ensure all credentials are valid | ||
for(const credential of credentials) { | ||
_checkCredential(credential); | ||
} | ||
presentation.verifiableCredential = credentials; | ||
} | ||
if(id) { | ||
presentation.id = id; | ||
} | ||
if(holder) { | ||
presentation.holder = holder; | ||
} | ||
_checkPresentation(presentation); | ||
return presentation; | ||
} | ||
/** | ||
* Signs a given presentation. | ||
* | ||
* @param {object} [options={}] - Options to use. | ||
* | ||
* Required: | ||
* @param {Presentation} options.presentation - A presentation. | ||
* @param {LinkedDataSignature} options.suite - passed in to sign() | ||
* | ||
* Either pass in a ProofPurpose, or a default one will be created with params: | ||
* @param {ProofPurpose} [options.purpose] - A ProofPurpose. If not specified, | ||
* a default purpose will be created with the domain and challenge options. | ||
* | ||
* @param {string} [options.domain] - A domain. | ||
* @param {string} options.challenge - A required challenge. | ||
* | ||
* @param {Function} [options.documentLoader] - A document loader. | ||
* | ||
* @returns {Promise<{VerifiablePresentation}>} A VerifiablePresentation with | ||
* a proof. | ||
*/ | ||
export async function signPresentation(options = {}) { | ||
const {presentation, domain, challenge} = options; | ||
const purpose = options.purpose || new AuthenticationProofPurpose({ | ||
domain, | ||
challenge | ||
}); | ||
const documentLoader = options.documentLoader || defaultDocumentLoader; | ||
return jsigs.sign(presentation, {purpose, documentLoader, ...options}); | ||
} | ||
/** | ||
* Verifies that the VerifiablePresentation is well formed, and checks the | ||
* proof signature if it's present. Also verifies all the VerifiableCredentials | ||
* that are present in the presentation, if any. | ||
* | ||
* @param {object} [options={}] - The options. | ||
* @param {VerifiablePresentation} options.presentation - A | ||
* VerifiablePresentation. | ||
* | ||
* @param {LinkedDataSignature|LinkedDataSignature[]} options.suite - See the | ||
* definition in the `verify()` docstring, for this param. | ||
* | ||
* @param {boolean} [options.unsignedPresentation=false] - By default, this | ||
* function assumes that a presentation is signed (and will return an error if | ||
* a `proof` section is missing). Set this to `true` if you're using an | ||
* unsigned presentation. | ||
* | ||
* Either pass in a proof purpose, | ||
* @param {AuthenticationProofPurpose} [options.presentationPurpose] - A | ||
* ProofPurpose. If not specified, a default purpose will be created with | ||
* the challenge, controller, and domain options. | ||
* | ||
* @param {string} [options.challenge] - A challenge. Required if purpose is | ||
* not passed in. | ||
* @param {string} [options.controller] - A controller. Required if purpose is | ||
* not passed in. | ||
* @param {string} [options.domain] - A domain. Required if purpose is not | ||
* passed in. | ||
* | ||
* @param {Function} [options.documentLoader] - A document loader. | ||
* @param {Function} [options.checkStatus] - Optional function for checking | ||
* credential status if `credentialStatus` is present on the credential. | ||
* | ||
* @throws {Error} If presentation is missing required params. | ||
* | ||
* @returns {Promise<VerifyPresentationResult>} The verification result. | ||
*/ | ||
async function _verifyPresentation(options = {}) { | ||
const {presentation, unsignedPresentation} = options; | ||
_checkPresentation(presentation); | ||
const documentLoader = options.documentLoader || defaultDocumentLoader; | ||
// FIXME: verify presentation first, then each individual credential | ||
// only if that proof is verified | ||
// if verifiableCredentials are present, verify them, individually | ||
let credentialResults; | ||
let verified = true; | ||
const credentials = jsonld.getValues(presentation, 'verifiableCredential'); | ||
if(credentials.length > 0) { | ||
// verify every credential in `verifiableCredential` | ||
credentialResults = await Promise.all(credentials.map(credential => { | ||
return verifyCredential({credential, documentLoader, ...options}); | ||
})); | ||
for(const [i, credentialResult] of credentialResults.entries()) { | ||
credentialResult.credentialId = credentials[i].id; | ||
} | ||
const allCredentialsVerified = credentialResults.every(r => r.verified); | ||
if(!allCredentialsVerified) { | ||
verified = false; | ||
} | ||
} | ||
if(unsignedPresentation) { | ||
// No need to verify the proof section of this presentation | ||
return {verified, results: [presentation], credentialResults}; | ||
} | ||
const {controller, domain, challenge} = options; | ||
if(!options.presentationPurpose && !challenge) { | ||
throw new Error( | ||
'A "challenge" param is required for AuthenticationProofPurpose.'); | ||
} | ||
const purpose = options.presentationPurpose || | ||
new AuthenticationProofPurpose({controller, domain, challenge}); | ||
const presentationResult = await jsigs.verify( | ||
presentation, {purpose, documentLoader, ...options}); | ||
return { | ||
presentationResult, | ||
verified: verified && presentationResult.verified, | ||
credentialResults, | ||
error: presentationResult.error | ||
}; | ||
} | ||
/** | ||
* @param {string|object} obj - Either an object with an id property | ||
* or a string that is an id. | ||
* @returns {string|undefined} Either an id or undefined. | ||
* @private | ||
* | ||
*/ | ||
function _getId(obj) { | ||
if(typeof obj === 'string') { | ||
return obj; | ||
} | ||
if(!('id' in obj)) { | ||
return; | ||
} | ||
return obj.id; | ||
} | ||
// export for testing | ||
/** | ||
* @param {object} presentation - An object that could be a presentation. | ||
* @throws {Error} | ||
* @private | ||
*/ | ||
export function _checkPresentation(presentation) { | ||
// normalize to an array to allow the common case of context being a string | ||
const context = Array.isArray(presentation['@context']) ? | ||
presentation['@context'] : [presentation['@context']]; | ||
// ensure first context is 'https://www.w3.org/2018/credentials/v1' | ||
if(context[0] !== CREDENTIALS_CONTEXT_V1_URL) { | ||
throw new Error( | ||
`"${CREDENTIALS_CONTEXT_V1_URL}" needs to be first in the ` + | ||
'list of contexts.'); | ||
} | ||
const types = jsonld.getValues(presentation, 'type'); | ||
// check type presence | ||
if(!types.includes('VerifiablePresentation')) { | ||
throw new Error('"type" must include "VerifiablePresentation".'); | ||
} | ||
} | ||
// export for testing | ||
/** | ||
* @param {object} credential - An object that could be a VerifiableCredential. | ||
* @throws {Error} | ||
* @private | ||
*/ | ||
export function _checkCredential(credential) { | ||
// ensure first context is 'https://www.w3.org/2018/credentials/v1' | ||
if(credential['@context'][0] !== CREDENTIALS_CONTEXT_V1_URL) { | ||
throw new Error( | ||
`"${CREDENTIALS_CONTEXT_V1_URL}" needs to be first in the ` + | ||
'list of contexts.'); | ||
} | ||
// check type presence and cardinality | ||
if(!credential.type) { | ||
throw new Error('"type" property is required.'); | ||
} | ||
if(!jsonld.getValues(credential, 'type').includes('VerifiableCredential')) { | ||
throw new Error('"type" must include `VerifiableCredential`.'); | ||
} | ||
if(!credential.credentialSubject) { | ||
throw new Error('"credentialSubject" property is required.'); | ||
} | ||
// If credentialSubject.id is present and is not a URI, reject it | ||
if(credential.credentialSubject.id) { | ||
_validateUriId({ | ||
id: credential.credentialSubject.id, propertyName: 'credentialSubject.id' | ||
}); | ||
} | ||
if(!credential.issuer) { | ||
throw new Error('"issuer" property is required.'); | ||
} | ||
// check issuanceDate cardinality | ||
if(jsonld.getValues(credential, 'issuanceDate').length > 1) { | ||
throw new Error('"issuanceDate" property can only have one value.'); | ||
} | ||
// check issued is a date | ||
if(!credential.issuanceDate) { | ||
throw new Error('"issuanceDate" property is required.'); | ||
} | ||
if('issuanceDate' in credential) { | ||
if(!dateRegex.test(credential.issuanceDate)) { | ||
throw new Error( | ||
`"issuanceDate" must be a valid date: ${credential.issuanceDate}`); | ||
} | ||
} | ||
// check issuer cardinality | ||
if(jsonld.getValues(credential, 'issuer').length > 1) { | ||
throw new Error('"issuer" property can only have one value.'); | ||
} | ||
// check issuer is a URL | ||
if('issuer' in credential) { | ||
const issuer = _getId(credential.issuer); | ||
if(!issuer) { | ||
throw new Error(`"issuer" id is required.`); | ||
} | ||
_validateUriId({id: issuer, propertyName: 'issuer'}); | ||
} | ||
if('credentialStatus' in credential) { | ||
if(!credential.credentialStatus.id) { | ||
throw new Error('"credentialStatus" must include an id.'); | ||
} | ||
if(!credential.credentialStatus.type) { | ||
throw new Error('"credentialStatus" must include a type.'); | ||
} | ||
} | ||
// check evidences are URLs | ||
jsonld.getValues(credential, 'evidence').forEach(evidence => { | ||
const evidenceId = _getId(evidence); | ||
if(evidenceId) { | ||
_validateUriId({id: evidenceId, propertyName: 'evidence'}); | ||
} | ||
}); | ||
// check expires is a date | ||
if('expirationDate' in credential && | ||
!dateRegex.test(credential.expirationDate)) { | ||
throw new Error( | ||
`"expirationDate" must be a valid date: ${credential.expirationDate}`); | ||
} | ||
} | ||
function _validateUriId({id, propertyName}) { | ||
let parsed; | ||
try { | ||
parsed = new URL(id); | ||
} catch(e) { | ||
const error = new TypeError(`"${propertyName}" must be a URI: "${id}".`); | ||
error.cause = e; | ||
throw error; | ||
} | ||
if(!parsed.protocol) { | ||
throw new TypeError(`"${propertyName}" must be a URI: "${id}".`); | ||
} | ||
} |
{ | ||
"name": "@digitalbazaar/vc", | ||
"version": "2.1.0", | ||
"version": "3.0.0", | ||
"description": "Verifiable Credentials JavaScript library.", | ||
@@ -25,3 +25,4 @@ "homepage": "https://github.com/digitalbazaar/vc-js", | ||
"license": "BSD-3-Clause", | ||
"main": "lib/index.js", | ||
"type": "module", | ||
"exports": "./lib/index.js", | ||
"files": [ | ||
@@ -32,27 +33,20 @@ "lib/**/*.js" | ||
"credentials-context": "^2.0.0", | ||
"jsonld": "^5.2.0", | ||
"jsonld-signatures": "^9.3.0" | ||
"jsonld": "^6.0.0", | ||
"jsonld-signatures": "^10.0.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.13.8", | ||
"@babel/plugin-transform-modules-commonjs": "^7.13.8", | ||
"@babel/plugin-transform-runtime": "^7.13.9", | ||
"@babel/preset-env": "^7.13.9", | ||
"@babel/runtime": "^7.13.9", | ||
"@digitalbazaar/ed25519-signature-2018": "^2.0.1", | ||
"@digitalbazaar/ed25519-signature-2020": "^3.0.0", | ||
"@digitalbazaar/ed25519-verification-key-2018": "^3.1.1", | ||
"@digitalbazaar/ed25519-verification-key-2020": "^3.1.0", | ||
"babel-loader": "^8.2.2", | ||
"chai": "^4.3.3", | ||
"@digitalbazaar/ed25519-signature-2018": "^3.0.0", | ||
"@digitalbazaar/ed25519-verification-key-2018": "^4.0.0", | ||
"c8": "^7.11.3", | ||
"chai": "^4.3.6", | ||
"cross-env": "^7.0.3", | ||
"did-context": "^3.1.1", | ||
"did-veres-one": "^13.0.0", | ||
"eslint": "^7.21.0", | ||
"eslint-config-digitalbazaar": "^2.6.1", | ||
"eslint-plugin-jsdoc": "^32.2.0", | ||
"karma": "^6.1.1", | ||
"karma-babel-preprocessor": "^8.0.1", | ||
"did-veres-one": "^15.0.0", | ||
"eslint": "^8.17.0", | ||
"eslint-config-digitalbazaar": "^3.0.0", | ||
"eslint-plugin-jsdoc": "^39.3.2", | ||
"eslint-plugin-unicorn": "^42.0.0", | ||
"karma": "^6.4.0", | ||
"karma-chai": "^0.1.0", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-chrome-launcher": "^3.1.1", | ||
"karma-mocha": "^2.0.1", | ||
@@ -62,25 +56,17 @@ "karma-mocha-reporter": "^2.2.5", | ||
"karma-webpack": "^5.0.0", | ||
"mocha": "^8.3.1", | ||
"mocha": "^10.0.0", | ||
"mocha-lcov-reporter": "^1.3.0", | ||
"nyc": "^15.1.0", | ||
"uuid": "^8.3.2", | ||
"veres-one-context": "^12.0.0", | ||
"webpack": "^5.24.3" | ||
"webpack": "^5.73.0" | ||
}, | ||
"nyc": { | ||
"exclude": [ | ||
"test" | ||
], | ||
"c8": { | ||
"reporter": [ | ||
"html", | ||
"text-summary" | ||
"lcov", | ||
"text-summary", | ||
"text" | ||
] | ||
}, | ||
"browser": { | ||
"Buffer": false, | ||
"crypto": false, | ||
"util": false | ||
}, | ||
"engines": { | ||
"node": ">=12" | ||
"node": ">=14" | ||
}, | ||
@@ -97,10 +83,10 @@ "keywords": [ | ||
"scripts": { | ||
"test": "npm run lint && npm run test-node && npm run test-karma", | ||
"test": "npm run test-node", | ||
"test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 test/*.spec.js", | ||
"test-karma": "karma start karma.conf.js", | ||
"lint": "eslint lib test/*.spec.js", | ||
"coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text-summary npm run test-node", | ||
"coverage-ci": "cross-env NODE_ENV=test nyc --reporter=lcovonly npm run test-node", | ||
"coverage-report": "nyc report" | ||
"test-karma": "karma start karma.conf.cjs", | ||
"lint": "eslint .", | ||
"coverage": "cross-env NODE_ENV=test c8 npm run test-node", | ||
"coverage-ci": "cross-env NODE_ENV=test c8 --reporter=lcovonly --reporter=text-summary --reporter=text npm run test-node", | ||
"coverage-report": "c8 report" | ||
} | ||
} |
@@ -51,4 +51,10 @@ # Verifiable Credentials JS Library _(@digitalbazaar/vc)_ | ||
- Node.js 12+ required. | ||
- Browsers and Node.js 14+ are supported. | ||
To install from NPM: | ||
``` | ||
npm install @digitalbazaar/vc | ||
``` | ||
To install locally (for development): | ||
@@ -55,0 +61,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23
902
397
Yes
52087
9
1
+ Added@digitalbazaar/http-client@3.4.1(transitive)
+ Added@fastify/busboy@2.1.1(transitive)
+ Addeddata-uri-to-buffer@4.0.1(transitive)
+ Addedfetch-blob@3.2.0(transitive)
+ Addedformdata-polyfill@4.0.10(transitive)
+ Addedjsonld@6.0.0(transitive)
+ Addedjsonld-signatures@10.0.0(transitive)
+ Addedky@0.33.3(transitive)
+ Addedky-universal@0.11.0(transitive)
+ Addednode-domexception@1.0.0(transitive)
+ Addednode-fetch@3.3.2(transitive)
+ Addedundici@5.28.4(transitive)
+ Addedweb-streams-polyfill@3.3.3(transitive)
- Removed@digitalbazaar/http-client@1.2.0(transitive)
- Removeddata-uri-to-buffer@3.0.1(transitive)
- Removedesm@3.2.25(transitive)
- Removedfetch-blob@2.1.2(transitive)
- Removedjsonld@5.2.0(transitive)
- Removedjsonld-signatures@9.3.1(transitive)
- Removedky@0.25.1(transitive)
- Removedky-universal@0.8.2(transitive)
- Removednode-fetch@3.0.0-beta.9(transitive)
Updatedjsonld@^6.0.0
Updatedjsonld-signatures@^10.0.0