@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 +5 -0
- package/lib/index.js +8 -1
- package/lib/inviteRequest/http.js +33 -0
- package/lib/inviteRequest/inviteRequest.js +116 -0
- package/lib/oid4/authorizationRequest.js +4 -1
- package/lib/vcapi.js +34 -3
- package/package.json +1 -1
- package/schemas/bedrock-vc-workflow.js +20 -0
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({
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
@@ -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',
|