@bedrock/vc-delivery 4.2.0 → 4.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/config.js +17 -5
- package/lib/exchanges.js +84 -38
- package/lib/helpers.js +15 -8
- package/lib/http.js +12 -154
- package/lib/index.js +66 -11
- package/lib/issue.js +17 -16
- package/lib/logger.js +2 -2
- package/lib/openId.js +173 -65
- package/lib/vcapi.js +170 -0
- package/lib/verify.js +98 -25
- package/package.json +1 -1
- package/schemas/{bedrock-vc-exchanger.js → bedrock-vc-workflow.js} +13 -1
package/lib/index.js
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
2
|
+
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
|
-
import * as
|
|
5
|
+
import * as workflowSchemas from '../schemas/bedrock-vc-workflow.js';
|
|
6
6
|
import {createService, schemas} from '@bedrock/service-core';
|
|
7
7
|
import {addRoutes} from './http.js';
|
|
8
8
|
import {initializeServiceAgent} from '@bedrock/service-agent';
|
|
9
9
|
import {klona} from 'klona';
|
|
10
|
+
import {parseLocalId} from './helpers.js';
|
|
10
11
|
import '@bedrock/express';
|
|
11
12
|
|
|
12
13
|
// load config defaults
|
|
13
14
|
import './config.js';
|
|
14
15
|
|
|
15
|
-
const serviceType = 'vc-exchanger';
|
|
16
16
|
const {util: {BedrockError}} = bedrock;
|
|
17
17
|
|
|
18
18
|
bedrock.events.on('bedrock.init', async () => {
|
|
19
|
+
await _initService({serviceType: 'vc-workflow', routePrefix: '/workflows'});
|
|
20
|
+
// backwards compatibility: deprecrated `exchangers` service
|
|
21
|
+
await _initService({serviceType: 'vc-exchanger', routePrefix: '/exchangers'});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
async function _initService({serviceType, routePrefix}) {
|
|
19
25
|
// add customizations to config validators...
|
|
20
26
|
const createConfigBody = klona(schemas.createConfigBody);
|
|
21
27
|
const updateConfigBody = klona(schemas.updateConfigBody);
|
|
22
28
|
const schemasToUpdate = [createConfigBody, updateConfigBody];
|
|
23
|
-
const {credentialTemplates, steps, initialStep} =
|
|
29
|
+
const {credentialTemplates, steps, initialStep} = workflowSchemas;
|
|
24
30
|
for(const schema of schemasToUpdate) {
|
|
25
|
-
// add config requirements to
|
|
31
|
+
// add config requirements to workflow configs
|
|
26
32
|
schema.properties.credentialTemplates = credentialTemplates;
|
|
27
33
|
schema.properties.steps = steps;
|
|
28
34
|
schema.properties.initialStep = initialStep;
|
|
@@ -31,10 +37,14 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
31
37
|
// schema.required.push('credentialTemplates');
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
//
|
|
40
|
+
// allow `id` property in `createConfigBody`, to be more rigorously validated
|
|
41
|
+
// below in `validateConfigFn`
|
|
42
|
+
createConfigBody.properties.id = updateConfigBody.properties.id;
|
|
43
|
+
|
|
44
|
+
// create workflow service
|
|
35
45
|
const service = await createService({
|
|
36
46
|
serviceType,
|
|
37
|
-
routePrefix
|
|
47
|
+
routePrefix,
|
|
38
48
|
storageCost: {
|
|
39
49
|
config: 1,
|
|
40
50
|
revocation: 1
|
|
@@ -42,7 +52,9 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
42
52
|
validation: {
|
|
43
53
|
createConfigBody,
|
|
44
54
|
updateConfigBody,
|
|
45
|
-
validateConfigFn,
|
|
55
|
+
async validateConfigFn({config, op} = {}) {
|
|
56
|
+
return validateConfigFn({config, op, routePrefix});
|
|
57
|
+
},
|
|
46
58
|
// these zcaps are optional (by reference ID)
|
|
47
59
|
zcapReferenceIds: [{
|
|
48
60
|
referenceId: 'issue',
|
|
@@ -67,7 +79,7 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
67
79
|
await addRoutes({app, service});
|
|
68
80
|
});
|
|
69
81
|
|
|
70
|
-
// initialize vc-
|
|
82
|
+
// initialize vc-workflow service agent early (after database is ready) if
|
|
71
83
|
// KMS system is externalized; otherwise we must wait until KMS system
|
|
72
84
|
// is ready
|
|
73
85
|
const externalKms = !bedrock.config['service-agent'].kms.baseUrl.startsWith(
|
|
@@ -76,7 +88,7 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
76
88
|
bedrock.events.on(event, async () => {
|
|
77
89
|
await initializeServiceAgent({serviceType});
|
|
78
90
|
});
|
|
79
|
-
}
|
|
91
|
+
}
|
|
80
92
|
|
|
81
93
|
async function usageAggregator({meter, signal, service} = {}) {
|
|
82
94
|
const {id: meterId} = meter;
|
|
@@ -84,8 +96,25 @@ async function usageAggregator({meter, signal, service} = {}) {
|
|
|
84
96
|
return service.configStorage.getUsage({meterId, signal});
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
async function validateConfigFn({config} = {}) {
|
|
99
|
+
async function validateConfigFn({config, op, routePrefix} = {}) {
|
|
88
100
|
try {
|
|
101
|
+
// validate any `id` in a new config
|
|
102
|
+
if(op === 'create' && config.id !== undefined) {
|
|
103
|
+
try {
|
|
104
|
+
_validateId({id: config.id, routePrefix});
|
|
105
|
+
} catch(e) {
|
|
106
|
+
throw new BedrockError(
|
|
107
|
+
`Invalid client-provided configuration ID: ${e.message}.`, {
|
|
108
|
+
name: 'DataError',
|
|
109
|
+
details: {
|
|
110
|
+
httpStatusCode: 400,
|
|
111
|
+
public: true
|
|
112
|
+
},
|
|
113
|
+
cause: e
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
89
118
|
// if credential templates are specified, then `zcaps` MUST include at
|
|
90
119
|
// least `issue`
|
|
91
120
|
const {credentialTemplates = [], zcaps = {}} = config;
|
|
@@ -118,3 +147,29 @@ async function validateConfigFn({config} = {}) {
|
|
|
118
147
|
}
|
|
119
148
|
return {valid: true};
|
|
120
149
|
}
|
|
150
|
+
|
|
151
|
+
function _validateId({id, routePrefix} = {}) {
|
|
152
|
+
// format: <base>/<localId>
|
|
153
|
+
|
|
154
|
+
// ensure `id` starts with appropriate base URL
|
|
155
|
+
const {baseUri} = bedrock.config.server;
|
|
156
|
+
const base = `${baseUri}${routePrefix}/`;
|
|
157
|
+
if(id.startsWith(base)) {
|
|
158
|
+
// ensure `id` ends with appropriate local ID
|
|
159
|
+
const expectedLastSlashIndex = base.length - 1;
|
|
160
|
+
const idx = id.lastIndexOf('/');
|
|
161
|
+
if(idx === expectedLastSlashIndex) {
|
|
162
|
+
return parseLocalId({id});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw new BedrockError(
|
|
167
|
+
`Configuration "id" must start with "${base}" and end in a multibase, ` +
|
|
168
|
+
'base58-encoded local identifier.', {
|
|
169
|
+
name: 'DataError',
|
|
170
|
+
details: {
|
|
171
|
+
httpStatusCode: 400,
|
|
172
|
+
public: true
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
package/lib/issue.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
2
|
+
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import {evaluateTemplate, getZcapClient} from './helpers.js';
|
|
5
5
|
import {createPresentation} from '@digitalbazaar/vc';
|
|
6
6
|
|
|
7
|
-
export async function issue({
|
|
8
|
-
// use any templates from
|
|
7
|
+
export async function issue({workflow, exchange} = {}) {
|
|
8
|
+
// use any templates from workflow and variables from exchange to produce
|
|
9
9
|
// credentials to be issued; issue via the configured issuer instance
|
|
10
10
|
const verifiableCredential = [];
|
|
11
|
-
const {credentialTemplates = []} =
|
|
11
|
+
const {credentialTemplates = []} = workflow;
|
|
12
12
|
if(!credentialTemplates || credentialTemplates.length === 0) {
|
|
13
13
|
// nothing to issue
|
|
14
14
|
return {};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// evaluate template
|
|
18
|
-
const
|
|
19
|
-
typedTemplate => evaluateTemplate({
|
|
18
|
+
const credentialRequests = await Promise.all(credentialTemplates.map(
|
|
19
|
+
typedTemplate => evaluateTemplate({workflow, exchange, typedTemplate})));
|
|
20
20
|
// issue all VCs
|
|
21
|
-
const vcs = await _issue({
|
|
21
|
+
const vcs = await _issue({workflow, credentialRequests});
|
|
22
22
|
verifiableCredential.push(...vcs);
|
|
23
23
|
|
|
24
24
|
// generate VP to return VCs
|
|
@@ -32,9 +32,9 @@ export async function issue({exchanger, exchange} = {}) {
|
|
|
32
32
|
return {verifiablePresentation};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async function _issue({
|
|
35
|
+
async function _issue({workflow, credentialRequests} = {}) {
|
|
36
36
|
// create zcap client for issuing VCs
|
|
37
|
-
const {zcapClient, zcaps} = await getZcapClient({
|
|
37
|
+
const {zcapClient, zcaps} = await getZcapClient({workflow});
|
|
38
38
|
|
|
39
39
|
// issue VCs in parallel
|
|
40
40
|
const capability = zcaps.issue;
|
|
@@ -42,14 +42,15 @@ async function _issue({exchanger, credentials} = {}) {
|
|
|
42
42
|
// is not specific to it
|
|
43
43
|
let url = capability.invocationTarget;
|
|
44
44
|
if(!capability.invocationTarget.endsWith('/credentials/issue')) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} else {
|
|
48
|
-
url += '/issue';
|
|
49
|
-
}
|
|
45
|
+
url += capability.invocationTarget.endsWith('/credentials') ?
|
|
46
|
+
'/issue' : '/credentials/issue';
|
|
50
47
|
}
|
|
51
|
-
const results = await Promise.all(
|
|
52
|
-
credential
|
|
48
|
+
const results = await Promise.all(credentialRequests.map(request => {
|
|
49
|
+
// normalize credential templates that return full VC API issue credential
|
|
50
|
+
// requests and those that return only the `credential` param directly
|
|
51
|
+
const json = request.credential ? request : {credential: request};
|
|
52
|
+
return zcapClient.write({url, capability, json});
|
|
53
|
+
}));
|
|
53
54
|
|
|
54
55
|
// parse VCs from results
|
|
55
56
|
const verifiableCredentials = results.map(
|
package/lib/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2020-
|
|
2
|
+
* Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import {loggers} from '@bedrock/core';
|
|
5
5
|
|
|
6
|
-
export const logger = loggers.get('app').child('bedrock-vc-
|
|
6
|
+
export const logger = loggers.get('app').child('bedrock-vc-workflow');
|