@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 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
+ }
@@ -1,10 +1,11 @@
1
1
  /*!
2
- * Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
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, evaluateTemplate, getWorkflowIssuerInstances, validateStep
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
  }
@@ -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, evaluateTemplate, unenvelopePresentation,
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, step} = arRequest;
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
- buildPresentationFromResults, evaluateTemplate, generateRandom,
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
- // validate the received VP
186
- const {jsonSchema: schema} = presentationSchema;
187
- const validate = compile({schema});
188
- const {valid, error} = validate(presentation);
189
- if(!valid) {
190
- throw error;
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.2.0",
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-2024 Digital Bazaar, Inc. All rights reserved.
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: verifiablePresentation()
566
+ verifiablePresentation: {
567
+ anyOf: [
568
+ envelopedVerifiablePresentation,
569
+ verifiablePresentation()
570
+ ]
571
+ }
536
572
  }
537
573
  };
538
574
  }