@bedrock/vc-delivery 7.5.0 → 7.7.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/http.js CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import * as bedrock from '@bedrock/core';
5
5
  import * as exchanges from './exchanges.js';
6
+ import * as inviteRequest from './inviteRequest/http.js';
6
7
  import * as oid4 from './oid4/http.js';
7
8
  import {createExchange, getProtocols, processExchange} from './vcapi.js';
8
9
  import {
@@ -147,6 +148,10 @@ export async function addRoutes({app, service} = {}) {
147
148
  res.json({protocols});
148
149
  }));
149
150
 
151
+ // create `inviteRequest` routes to be used with each individual exchange
152
+ await inviteRequest.createRoutes(
153
+ {app, exchangeRoute: routes.exchange, getConfigMiddleware, getExchange});
154
+
150
155
  // create OID4* routes to be used with each individual exchange
151
156
  await oid4.createRoutes(
152
157
  {app, exchangeRoute: routes.exchange, getConfigMiddleware, getExchange});
package/lib/index.js CHANGED
@@ -17,8 +17,13 @@ import './config.js';
17
17
 
18
18
  const {util: {BedrockError}} = bedrock;
19
19
 
20
+ // export programmatic access to workflow service
21
+ export let workflowService;
22
+
20
23
  bedrock.events.on('bedrock.init', async () => {
21
- await _initService({serviceType: 'vc-workflow', routePrefix: '/workflows'});
24
+ workflowService = await _initService({
25
+ serviceType: 'vc-workflow', routePrefix: '/workflows'
26
+ });
22
27
  // backwards compatibility: deprecrated `exchangers` service
23
28
  await _initService({serviceType: 'vc-exchanger', routePrefix: '/exchangers'});
24
29
  });
@@ -102,6 +107,8 @@ async function _initService({serviceType, routePrefix}) {
102
107
  bedrock.events.on(event, async () => {
103
108
  await initializeServiceAgent({serviceType});
104
109
  });
110
+
111
+ return service;
105
112
  }
106
113
 
107
114
  async function usageAggregator({meter, signal, service} = {}) {
@@ -0,0 +1,33 @@
1
+ /*!
2
+ * Copyright (c) 2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as inviteRequest from './inviteRequest.js';
5
+ import {asyncHandler} from '@bedrock/express';
6
+ import cors from 'cors';
7
+ import {inviteResponseBody} from '../../schemas/bedrock-vc-workflow.js';
8
+ import {createValidateMiddleware as validate} from '@bedrock/validation';
9
+
10
+ // creates invite request endpoints for each individual exchange
11
+ export async function createRoutes({
12
+ app, exchangeRoute, getConfigMiddleware, getExchange
13
+ } = {}) {
14
+ const inviteRequestRoute = `${exchangeRoute}/invite-request`;
15
+ const routes = {
16
+ inviteResponse: `${inviteRequestRoute}/response`
17
+ };
18
+
19
+ // receives an invite response
20
+ app.options(routes.inviteResponse, cors());
21
+ app.post(
22
+ routes.inviteResponse,
23
+ cors(),
24
+ validate({bodySchema: inviteResponseBody()}),
25
+ getConfigMiddleware,
26
+ getExchange,
27
+ asyncHandler(_handleInviteResponse));
28
+ }
29
+
30
+ async function _handleInviteResponse(req, res) {
31
+ const result = await inviteRequest.processInviteResponse({req});
32
+ res.json(result);
33
+ }
@@ -0,0 +1,116 @@
1
+ /*!
2
+ * Copyright (c) 2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as bedrock from '@bedrock/core';
5
+ import * as exchanges from '../exchanges.js';
6
+ import {emitExchangeUpdated, evaluateExchangeStep} from '../helpers.js';
7
+ import {logger} from '../logger.js';
8
+
9
+ const {util: {BedrockError}} = bedrock;
10
+
11
+ export async function processInviteResponse({req}) {
12
+ const {config: workflow} = req.serviceObject;
13
+ const exchangeRecord = await req.getExchange();
14
+ let {meta: {updated: lastUpdated}} = exchangeRecord;
15
+ const {exchange} = exchangeRecord;
16
+ let step;
17
+
18
+ try {
19
+ // exchange step required for `inviteRequest`
20
+ const currentStep = exchange.step;
21
+ if(!currentStep) {
22
+ _throwUnsupportedProtocol();
23
+ }
24
+
25
+ step = await evaluateExchangeStep({workflow, exchange});
26
+
27
+ // step must have `inviteRequest` to perform protocol
28
+ if(!step.inviteRequest) {
29
+ _throwUnsupportedProtocol();
30
+ }
31
+
32
+ // exchange must still be pending
33
+ if(exchange.state !== 'pending') {
34
+ throw new BedrockError(
35
+ 'This exchange is already in progress.', {
36
+ name: 'NotAllowedError',
37
+ details: {httpStatusCode: 403, public: true}
38
+ });
39
+ }
40
+
41
+ // `inviteResponse` validated via HTTP body JSON schema already
42
+ const {body: inviteResponse} = req;
43
+
44
+ // store invite response in variables associated with current step
45
+ if(!exchange.variables.results) {
46
+ exchange.variables.results = {};
47
+ }
48
+ const stepResult = {
49
+ inviteRequest: {inviteResponse}
50
+ };
51
+ const prevState = exchange.state;
52
+ exchange.variables.results[currentStep] = stepResult;
53
+ try {
54
+ // mark exchange complete
55
+ exchange.state = 'complete';
56
+ exchange.sequence++;
57
+ await exchanges.complete({workflowId: workflow.id, exchange});
58
+ emitExchangeUpdated({workflow, exchange, step});
59
+ lastUpdated = Date.now();
60
+ } catch(e) {
61
+ // revert exchange changes as it couldn't be written
62
+ exchange.sequence--;
63
+ exchange.state = prevState;
64
+ delete exchange.variables.results[currentStep];
65
+ throw e;
66
+ }
67
+
68
+ const result = {};
69
+ if(inviteResponse.referenceId !== undefined) {
70
+ result.referenceId = inviteResponse.referenceId;
71
+ }
72
+ return result;
73
+ } catch(e) {
74
+ if(e.name === 'InvalidStateError') {
75
+ throw e;
76
+ }
77
+ // write last error if exchange hasn't been frequently updated
78
+ const {id: workflowId} = workflow;
79
+ const copy = {...exchange};
80
+ copy.sequence++;
81
+ copy.lastError = e;
82
+ exchanges.setLastError({workflowId, exchange: copy, lastUpdated})
83
+ .catch(error => logger.error(
84
+ 'Could not set last exchange error: ' + error.message, {error}));
85
+ emitExchangeUpdated({workflow, exchange, step});
86
+ throw e;
87
+ }
88
+ }
89
+
90
+ export function getInviteRequestProtocols({workflow, exchange, step}) {
91
+ // no invite protocols supported
92
+ if(!supportsInviteRequest({workflow, exchange, step})) {
93
+ return {};
94
+ }
95
+
96
+ const exchangeId = `${workflow.id}/exchanges/${exchange.id}`;
97
+ return {inviteRequest: `${exchangeId}/invite-request/response`};
98
+ }
99
+
100
+ export async function initExchange({workflow, exchange, initialStep} = {}) {
101
+ if(!supportsInviteRequest({workflow, exchange, step: initialStep})) {
102
+ return;
103
+ }
104
+ // no special validation rules at this time
105
+ }
106
+
107
+ export function supportsInviteRequest({step} = {}) {
108
+ return !!step?.inviteRequest;
109
+ }
110
+
111
+ function _throwUnsupportedProtocol() {
112
+ throw new BedrockError('Invite request is not supported by this exchange.', {
113
+ name: 'NotSupportedError',
114
+ details: {httpStatusCode: 400, public: true}
115
+ });
116
+ }
@@ -17,7 +17,10 @@ export async function create({
17
17
  clientProfile, clientProfileId,
18
18
  verifiablePresentationRequest
19
19
  }) {
20
- const authorizationRequest = oid4vp.fromVpr({verifiablePresentationRequest});
20
+ const authorizationRequest = verifiablePresentationRequest ?
21
+ oid4vp.fromVpr({verifiablePresentationRequest}) :
22
+ // default authz request
23
+ {response_type: 'vp_token'};
21
24
 
22
25
  // get params from step OID4VP client profile to apply to the AR
23
26
  const {
package/lib/vcapi.js CHANGED
@@ -14,6 +14,7 @@ import {issue} from './issue.js';
14
14
  import {logger} from './logger.js';
15
15
 
16
16
  // supported protocols
17
+ import * as inviteRequest from './inviteRequest/inviteRequest.js';
17
18
  import * as oid4vci from './oid4/oid4vci.js';
18
19
  import * as oid4vp from './oid4/oid4vp.js';
19
20
 
@@ -77,6 +78,7 @@ export async function createExchange({workflow, exchange}) {
77
78
  }
78
79
 
79
80
  // run protocol-specific initialization code
81
+ await inviteRequest.initExchange({workflow, exchange, initialStep});
80
82
  await oid4vci.initExchange({workflow, exchange, initialStep});
81
83
  await oid4vp.initExchange({workflow, exchange, initialStep});
82
84
 
@@ -137,6 +139,15 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
137
139
  workflow, exchange, stepName: currentStep
138
140
  });
139
141
 
142
+ // if step does not support VCAPI, throw
143
+ if(!_supportsVcApi({workflow, step})) {
144
+ throw new BedrockError(
145
+ 'VC API protocol not supported by this exchange.', {
146
+ name: 'NotSupportedError',
147
+ details: {httpStatusCode: 400, public: true}
148
+ });
149
+ }
150
+
140
151
  // if next step is the same as the current step, throw an error
141
152
  if(step.nextStep === currentStep) {
142
153
  throw new BedrockError('Cyclical step detected.', {
@@ -376,7 +387,21 @@ async function _createProtocols({workflow, exchange, step}) {
376
387
  // dynamically construct and return `protocols` object...
377
388
  const exchangeId = `${workflow.id}/exchanges/${exchange.id}`;
378
389
 
379
- // get OID4 protocols...
390
+ // VC API protocols...
391
+ let vcApiProtocols;
392
+ if(_supportsVcApi({workflow, step})) {
393
+ vcApiProtocols = {vcapi: exchangeId};
394
+ }
395
+
396
+ // invite request protocols
397
+ let inviteRequestProtocols;
398
+ if(inviteRequest.supportsInviteRequest({step})) {
399
+ inviteRequestProtocols = inviteRequest.getInviteRequestProtocols({
400
+ workflow, exchange, step
401
+ });
402
+ }
403
+
404
+ // OID4* protocols...
380
405
  let oid4vciProtocols;
381
406
  let oid4vpProtocols;
382
407
  if(oid4vci.supportsOID4VCI({exchange})) {
@@ -393,9 +418,15 @@ async function _createProtocols({workflow, exchange, step}) {
393
418
 
394
419
  // return merged protocols
395
420
  return {
421
+ ...inviteRequestProtocols,
396
422
  ...oid4vciProtocols,
397
423
  ...oid4vpProtocols,
398
- // always add VC API protocol
399
- vcapi: exchangeId
424
+ ...vcApiProtocols
400
425
  };
401
426
  }
427
+
428
+ function _supportsVcApi({workflow, step}) {
429
+ return step?.verifiablePresentationRequest ||
430
+ step?.issueRequests?.length > 0 ||
431
+ workflow?.credentialTemplates?.length > 0;
432
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "7.5.0",
3
+ "version": "7.7.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",
@@ -395,6 +395,26 @@ const issueRequestParameters = {
395
395
  }
396
396
  };
397
397
 
398
+ export function inviteResponseBody() {
399
+ return {
400
+ title: 'Invite Response',
401
+ type: 'object',
402
+ additionalProperties: false,
403
+ required: ['url', 'purpose'],
404
+ properties: {
405
+ url: {
406
+ type: 'string'
407
+ },
408
+ purpose: {
409
+ type: 'string'
410
+ },
411
+ referenceId: {
412
+ type: 'string'
413
+ }
414
+ }
415
+ };
416
+ }
417
+
398
418
  const oid4vpClientProfile = {
399
419
  title: 'OID4VP Client Profile',
400
420
  type: 'object',