@bedrock/vc-delivery 7.2.0 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/helpers.js +32 -0
- package/lib/oid4/oid4vci.js +6 -3
- package/lib/oid4/oid4vp.js +8 -3
- package/lib/vcapi.js +23 -22
- package/package.json +2 -1
- package/schemas/bedrock-vc-workflow.js +38 -2
package/lib/helpers.js
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
5
|
import * as vcjwt from './vcjwt.js';
|
|
6
6
|
import {decodeId, generateId} from 'bnid';
|
|
7
|
+
import {compile} from '@bedrock/validation';
|
|
7
8
|
import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020';
|
|
9
|
+
import {httpClient} from '@digitalbazaar/http-client';
|
|
8
10
|
import {httpsAgent} from '@bedrock/https-agent';
|
|
9
11
|
import jsonata from 'jsonata';
|
|
12
|
+
import {logger} from './logger.js';
|
|
10
13
|
import {serializeError} from 'serialize-error';
|
|
11
14
|
import {serviceAgents} from '@bedrock/service-agent';
|
|
12
15
|
import {ZcapClient} from '@digitalbazaar/ezcap';
|
|
@@ -53,6 +56,27 @@ export function buildPresentationFromResults({
|
|
|
53
56
|
return vp;
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
export function emitExchangeUpdated({workflow, exchange, step}) {
|
|
60
|
+
if(!step?.callback?.url) {
|
|
61
|
+
// no-op when there is no callback to notify
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const {url} = step.callback;
|
|
66
|
+
const exchangeId = `${workflow.id}/exchanges/${exchange.id}`;
|
|
67
|
+
httpClient.post(url, {
|
|
68
|
+
agent: httpsAgent,
|
|
69
|
+
json: {
|
|
70
|
+
event: {
|
|
71
|
+
data: {exchangeId}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}).catch(
|
|
75
|
+
error => logger.error(
|
|
76
|
+
'Could not send "exchangeUpdated" push notification: ' +
|
|
77
|
+
error.message, {error}));
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
export async function evaluateTemplate({
|
|
57
81
|
workflow, exchange, typedTemplate, variables
|
|
58
82
|
} = {}) {
|
|
@@ -329,3 +353,11 @@ function _getEnvelope({envelope, format}) {
|
|
|
329
353
|
details: {httpStatusCode: 400, public: true}
|
|
330
354
|
});
|
|
331
355
|
}
|
|
356
|
+
|
|
357
|
+
export function validateVerifiablePresentation({schema, presentation}) {
|
|
358
|
+
const validate = compile({schema});
|
|
359
|
+
const {valid, error} = validate(presentation);
|
|
360
|
+
if(!valid) {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
package/lib/oid4/oid4vci.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
2
|
+
* Copyright (c) 2022-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
5
|
import * as exchanges from '../exchanges.js';
|
|
6
6
|
import {
|
|
7
|
-
deepEqual,
|
|
7
|
+
deepEqual, emitExchangeUpdated,
|
|
8
|
+
evaluateTemplate, getWorkflowIssuerInstances, validateStep
|
|
8
9
|
} from '../helpers.js';
|
|
9
10
|
import {importJWK, SignJWT} from 'jose';
|
|
10
11
|
import {checkAccessToken} from '@bedrock/oauth2-verifier';
|
|
@@ -397,6 +398,7 @@ function _normalizeCredentialDefinitionTypes({credentialRequests}) {
|
|
|
397
398
|
async function _processExchange({
|
|
398
399
|
req, res, workflow, exchangeRecord, isBatchRequest
|
|
399
400
|
}) {
|
|
401
|
+
let step;
|
|
400
402
|
const {id: workflowId} = workflow;
|
|
401
403
|
const {exchange, meta} = exchangeRecord;
|
|
402
404
|
let {updated: lastUpdated} = meta;
|
|
@@ -428,7 +430,6 @@ async function _processExchange({
|
|
|
428
430
|
});
|
|
429
431
|
|
|
430
432
|
// process exchange step if present
|
|
431
|
-
let step;
|
|
432
433
|
const currentStep = exchange.step;
|
|
433
434
|
if(currentStep) {
|
|
434
435
|
step = workflow.steps[exchange.step];
|
|
@@ -494,6 +495,7 @@ async function _processExchange({
|
|
|
494
495
|
try {
|
|
495
496
|
exchange.sequence++;
|
|
496
497
|
await exchanges.complete({workflowId, exchange});
|
|
498
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
497
499
|
lastUpdated = Date.now();
|
|
498
500
|
} catch(e) {
|
|
499
501
|
exchange.sequence--;
|
|
@@ -516,6 +518,7 @@ async function _processExchange({
|
|
|
516
518
|
exchanges.setLastError({workflowId, exchange: copy, lastUpdated})
|
|
517
519
|
.catch(error => logger.error(
|
|
518
520
|
'Could not set last exchange error: ' + error.message, {error}));
|
|
521
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
519
522
|
throw e;
|
|
520
523
|
}
|
|
521
524
|
}
|
package/lib/oid4/oid4vp.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
5
|
import * as exchanges from '../exchanges.js';
|
|
6
6
|
import {
|
|
7
|
-
buildPresentationFromResults,
|
|
7
|
+
buildPresentationFromResults, emitExchangeUpdated,
|
|
8
|
+
evaluateTemplate, unenvelopePresentation,
|
|
8
9
|
validateStep
|
|
9
10
|
} from '../helpers.js';
|
|
10
11
|
import {
|
|
@@ -141,6 +142,7 @@ export async function getAuthorizationRequest({req}) {
|
|
|
141
142
|
try {
|
|
142
143
|
exchange.sequence++;
|
|
143
144
|
await exchanges.update({workflowId: workflow.id, exchange});
|
|
145
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
144
146
|
} catch(e) {
|
|
145
147
|
exchange.sequence--;
|
|
146
148
|
if(e.name !== 'InvalidStateError') {
|
|
@@ -168,11 +170,12 @@ export async function processAuthorizationResponse({req}) {
|
|
|
168
170
|
const exchangeRecord = await req.getExchange();
|
|
169
171
|
let {exchange} = exchangeRecord;
|
|
170
172
|
let {meta: {updated: lastUpdated}} = exchangeRecord;
|
|
173
|
+
let step;
|
|
171
174
|
try {
|
|
172
175
|
// get authorization request and updated exchange associated with exchange
|
|
173
176
|
const arRequest = await getAuthorizationRequest({req});
|
|
174
|
-
const {authorizationRequest
|
|
175
|
-
({exchange} = arRequest);
|
|
177
|
+
const {authorizationRequest} = arRequest;
|
|
178
|
+
({exchange, step} = arRequest);
|
|
176
179
|
|
|
177
180
|
// FIXME: check the VP against the presentation submission if requested
|
|
178
181
|
// FIXME: check the VP against "trustedIssuer" in VPR, if provided
|
|
@@ -250,6 +253,7 @@ export async function processAuthorizationResponse({req}) {
|
|
|
250
253
|
exchange.state = 'complete';
|
|
251
254
|
await exchanges.complete({workflowId: workflow.id, exchange});
|
|
252
255
|
}
|
|
256
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
253
257
|
lastUpdated = Date.now();
|
|
254
258
|
} catch(e) {
|
|
255
259
|
exchange.sequence--;
|
|
@@ -277,6 +281,7 @@ export async function processAuthorizationResponse({req}) {
|
|
|
277
281
|
exchanges.setLastError({workflowId, exchange: copy, lastUpdated})
|
|
278
282
|
.catch(error => logger.error(
|
|
279
283
|
'Could not set last exchange error: ' + error.message, {error}));
|
|
284
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
280
285
|
throw e;
|
|
281
286
|
}
|
|
282
287
|
}
|
package/lib/vcapi.js
CHANGED
|
@@ -4,12 +4,10 @@
|
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
5
|
import * as exchanges from './exchanges.js';
|
|
6
6
|
import {createChallenge as _createChallenge, verify} from './verify.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
unenvelopePresentation, validateStep
|
|
7
|
+
import {buildPresentationFromResults, emitExchangeUpdated, evaluateTemplate,
|
|
8
|
+
generateRandom, validateStep, validateVerifiablePresentation
|
|
10
9
|
} from './helpers.js';
|
|
11
10
|
import {exportJWK, generateKeyPair, importJWK} from 'jose';
|
|
12
|
-
import {compile} from '@bedrock/validation';
|
|
13
11
|
import {issue} from './issue.js';
|
|
14
12
|
import {logger} from './logger.js';
|
|
15
13
|
|
|
@@ -93,6 +91,8 @@ export async function createExchange({workflow, exchange}) {
|
|
|
93
91
|
export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
94
92
|
const {exchange, meta} = exchangeRecord;
|
|
95
93
|
let {updated: lastUpdated} = meta;
|
|
94
|
+
let step;
|
|
95
|
+
|
|
96
96
|
try {
|
|
97
97
|
// get any `verifiablePresentation` from the body...
|
|
98
98
|
let receivedPresentation = req?.body?.verifiablePresentation;
|
|
@@ -100,7 +100,6 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
|
100
100
|
// process exchange step(s)
|
|
101
101
|
let i = 0;
|
|
102
102
|
let currentStep = exchange.step;
|
|
103
|
-
let step;
|
|
104
103
|
while(true) {
|
|
105
104
|
if(i++ > MAXIMUM_STEPS) {
|
|
106
105
|
throw new BedrockError('Maximum steps exceeded.', {
|
|
@@ -171,24 +170,15 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
const {presentationSchema} = step;
|
|
174
|
-
if(presentationSchema) {
|
|
175
|
-
// if the VP is enveloped, get the presentation from the envelope
|
|
176
|
-
let presentation;
|
|
177
|
-
if(receivedPresentation?.type === 'EnvelopedVerifiablePresentation') {
|
|
178
|
-
({presentation} = await unenvelopePresentation({
|
|
179
|
-
envelopedPresentation: receivedPresentation
|
|
180
|
-
}));
|
|
181
|
-
} else {
|
|
182
|
-
presentation = receivedPresentation;
|
|
183
|
-
}
|
|
184
173
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
174
|
+
const isEnvelopedVP =
|
|
175
|
+
receivedPresentation?.type === 'EnvelopedVerifiablePresentation';
|
|
176
|
+
|
|
177
|
+
if(presentationSchema && !isEnvelopedVP) {
|
|
178
|
+
validateVerifiablePresentation({
|
|
179
|
+
schema: presentationSchema.jsonSchema,
|
|
180
|
+
presentation: receivedPresentation
|
|
181
|
+
});
|
|
192
182
|
}
|
|
193
183
|
|
|
194
184
|
// verify the received VP
|
|
@@ -208,6 +198,14 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
|
208
198
|
expectedChallenge
|
|
209
199
|
});
|
|
210
200
|
|
|
201
|
+
// validate enveloped VP after verification
|
|
202
|
+
if(presentationSchema && isEnvelopedVP) {
|
|
203
|
+
validateVerifiablePresentation({
|
|
204
|
+
schema: presentationSchema.jsonSchema,
|
|
205
|
+
presentation: verifyResult?.presentationResult?.presentation ?? {}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
211
209
|
// store VP results in variables associated with current step
|
|
212
210
|
if(!exchange.variables.results) {
|
|
213
211
|
exchange.variables.results = {};
|
|
@@ -255,6 +253,7 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
|
255
253
|
try {
|
|
256
254
|
exchange.sequence++;
|
|
257
255
|
await exchanges.update({workflowId: workflow.id, exchange});
|
|
256
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
258
257
|
lastUpdated = Date.now();
|
|
259
258
|
} catch(e) {
|
|
260
259
|
exchange.sequence--;
|
|
@@ -279,6 +278,7 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
|
279
278
|
try {
|
|
280
279
|
exchange.sequence++;
|
|
281
280
|
await exchanges.complete({workflowId: workflow.id, exchange});
|
|
281
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
282
282
|
} catch(e) {
|
|
283
283
|
exchange.sequence--;
|
|
284
284
|
throw e;
|
|
@@ -311,6 +311,7 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
|
|
|
311
311
|
exchanges.setLastError({workflowId, exchange: copy, lastUpdated})
|
|
312
312
|
.catch(error => logger.error(
|
|
313
313
|
'Could not set last exchange error: ' + error.message, {error}));
|
|
314
|
+
emitExchangeUpdated({workflow, exchange, step});
|
|
314
315
|
throw e;
|
|
315
316
|
}
|
|
316
317
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrock/vc-delivery",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Bedrock Verifiable Credential Delivery",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@digitalbazaar/ed25519-multikey": "^1.3.1",
|
|
40
40
|
"@digitalbazaar/ed25519-signature-2020": "^5.4.0",
|
|
41
41
|
"@digitalbazaar/ezcap": "^4.1.0",
|
|
42
|
+
"@digitalbazaar/http-client": "^4.2.0",
|
|
42
43
|
"@digitalbazaar/oid4-client": "^4.3.0",
|
|
43
44
|
"@digitalbazaar/vc": "^7.2.0",
|
|
44
45
|
"assert-plus": "^1.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
2
|
+
* Copyright (c) 2022-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import {MAX_ISSUER_INSTANCES} from '../lib/constants.js';
|
|
5
5
|
import {schemas} from '@bedrock/validation';
|
|
@@ -116,6 +116,26 @@ const envelopedVerifiableCredential = {
|
|
|
116
116
|
]
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
+
const envelopedVerifiablePresentation = {
|
|
120
|
+
title: 'Enveloped Verifiable Presentation',
|
|
121
|
+
type: 'object',
|
|
122
|
+
additionalProperties: true,
|
|
123
|
+
properties: {
|
|
124
|
+
'@context': vcContext2StringOrArray,
|
|
125
|
+
id: {
|
|
126
|
+
type: 'string'
|
|
127
|
+
},
|
|
128
|
+
type: {
|
|
129
|
+
const: 'EnvelopedVerifiablePresentation'
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
required: [
|
|
133
|
+
'@context',
|
|
134
|
+
'id',
|
|
135
|
+
'type'
|
|
136
|
+
]
|
|
137
|
+
};
|
|
138
|
+
|
|
119
139
|
export function verifiablePresentation() {
|
|
120
140
|
return {
|
|
121
141
|
title: 'Verifiable Presentation',
|
|
@@ -381,6 +401,7 @@ const step = {
|
|
|
381
401
|
not: {
|
|
382
402
|
required: [
|
|
383
403
|
'allowUnprotectedPresentation',
|
|
404
|
+
'callback',
|
|
384
405
|
'createChallenge',
|
|
385
406
|
'issueRequests',
|
|
386
407
|
'jwtDidProofRequest',
|
|
@@ -400,6 +421,16 @@ const step = {
|
|
|
400
421
|
allowUnprotectedPresentation: {
|
|
401
422
|
type: 'boolean'
|
|
402
423
|
},
|
|
424
|
+
callback: {
|
|
425
|
+
type: 'object',
|
|
426
|
+
required: ['url'],
|
|
427
|
+
additionalProperties: false,
|
|
428
|
+
properties: {
|
|
429
|
+
url: {
|
|
430
|
+
type: 'string'
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
},
|
|
403
434
|
createChallenge: {
|
|
404
435
|
type: 'boolean'
|
|
405
436
|
},
|
|
@@ -532,7 +563,12 @@ export function useExchangeBody() {
|
|
|
532
563
|
type: 'object',
|
|
533
564
|
additionalProperties: false,
|
|
534
565
|
properties: {
|
|
535
|
-
verifiablePresentation:
|
|
566
|
+
verifiablePresentation: {
|
|
567
|
+
anyOf: [
|
|
568
|
+
envelopedVerifiablePresentation,
|
|
569
|
+
verifiablePresentation()
|
|
570
|
+
]
|
|
571
|
+
}
|
|
536
572
|
}
|
|
537
573
|
};
|
|
538
574
|
}
|