@bedrock/vc-delivery 4.6.0 → 4.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/constants.js +5 -0
- package/lib/helpers.js +14 -0
- package/lib/http.js +39 -24
- package/lib/index.js +40 -12
- package/lib/issue.js +42 -18
- package/lib/openId.js +93 -25
- package/lib/vcapi.js +5 -5
- package/package.json +1 -1
- package/schemas/bedrock-vc-workflow.js +65 -13
package/lib/constants.js
ADDED
package/lib/helpers.js
CHANGED
|
@@ -48,6 +48,20 @@ export async function generateRandom() {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export function getWorkflowIssuerInstances({workflow} = {}) {
|
|
52
|
+
let {issuerInstances} = workflow;
|
|
53
|
+
if(!issuerInstances && workflow.zcaps.issue) {
|
|
54
|
+
// generate dynamic issuer instance config
|
|
55
|
+
issuerInstances = [{
|
|
56
|
+
supportedFormats: ['application/vc', 'ldp_vc'],
|
|
57
|
+
zcapReferenceIds: {
|
|
58
|
+
issue: 'issue'
|
|
59
|
+
}
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
return issuerInstances;
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
export async function getZcapClient({workflow} = {}) {
|
|
52
66
|
// get service agent for communicating with the issuer instance
|
|
53
67
|
const {pathname} = new URL(workflow.id);
|
package/lib/http.js
CHANGED
|
@@ -88,31 +88,46 @@ export async function addRoutes({app, service} = {}) {
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
if(openId) {
|
|
92
|
+
// either issuer instances or a single issuer zcap be given if
|
|
93
|
+
// any expected credential requests are given
|
|
94
|
+
const {expectedCredentialRequests} = openId;
|
|
95
|
+
if(expectedCredentialRequests &&
|
|
96
|
+
!(config.issuerInstances || config.zcaps.issue)) {
|
|
97
|
+
throw new BedrockError(
|
|
98
|
+
'Credential requests are not supported by this workflow.', {
|
|
99
|
+
name: 'DataError',
|
|
100
|
+
details: {httpStatusCode: 400, public: true}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// perform key generation if requested
|
|
105
|
+
if(openId.oauth2?.generateKeyPair) {
|
|
106
|
+
const {oauth2} = openId;
|
|
107
|
+
const {algorithm} = oauth2.generateKeyPair;
|
|
108
|
+
const kp = await generateKeyPair(algorithm, {extractable: true});
|
|
109
|
+
const [privateKeyJwk, publicKeyJwk] = await Promise.all([
|
|
110
|
+
exportJWK(kp.privateKey),
|
|
111
|
+
exportJWK(kp.publicKey),
|
|
109
112
|
]);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
oauth2.keyPair = {privateKeyJwk, publicKeyJwk};
|
|
114
|
+
delete oauth2.generateKeyPair;
|
|
115
|
+
} else {
|
|
116
|
+
// ensure key pair can be imported
|
|
117
|
+
try {
|
|
118
|
+
const {oauth2: {keyPair}} = openId;
|
|
119
|
+
await Promise.all([
|
|
120
|
+
importJWK(keyPair.privateKeyJwk),
|
|
121
|
+
importJWK(keyPair.publicKeyJwk)
|
|
122
|
+
]);
|
|
123
|
+
} catch(e) {
|
|
124
|
+
throw new BedrockError(
|
|
125
|
+
'Could not import OpenID OAuth2 key pair.', {
|
|
126
|
+
name: 'DataError',
|
|
127
|
+
details: {httpStatusCode: 400, public: true},
|
|
128
|
+
cause: e
|
|
129
|
+
});
|
|
130
|
+
}
|
|
116
131
|
}
|
|
117
132
|
}
|
|
118
133
|
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ 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 {MAX_ISSUER_INSTANCES} from './constants.js';
|
|
10
11
|
import {parseLocalId} from './helpers.js';
|
|
11
12
|
import '@bedrock/express';
|
|
12
13
|
|
|
@@ -26,12 +27,20 @@ async function _initService({serviceType, routePrefix}) {
|
|
|
26
27
|
const createConfigBody = klona(schemas.createConfigBody);
|
|
27
28
|
const updateConfigBody = klona(schemas.updateConfigBody);
|
|
28
29
|
const schemasToUpdate = [createConfigBody, updateConfigBody];
|
|
29
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
credentialTemplates, steps, initialStep, issuerInstances
|
|
32
|
+
} = workflowSchemas;
|
|
30
33
|
for(const schema of schemasToUpdate) {
|
|
31
34
|
// add config requirements to workflow configs
|
|
32
35
|
schema.properties.credentialTemplates = credentialTemplates;
|
|
33
36
|
schema.properties.steps = steps;
|
|
34
37
|
schema.properties.initialStep = initialStep;
|
|
38
|
+
schema.properties.issuerInstances = issuerInstances;
|
|
39
|
+
// allow zcaps by custom reference ID
|
|
40
|
+
schema.properties.zcaps = klona(schemas.zcaps);
|
|
41
|
+
// max of 4 basic zcaps + max issuer instances
|
|
42
|
+
schema.properties.zcaps.maxProperties = 4 + MAX_ISSUER_INSTANCES;
|
|
43
|
+
schema.properties.zcaps.additionalProperties = schemas.delegatedZcap;
|
|
35
44
|
// note: credential templates are not required; if any other properties
|
|
36
45
|
// become required, add them here
|
|
37
46
|
// schema.required.push('credentialTemplates');
|
|
@@ -117,17 +126,36 @@ async function validateConfigFn({config, op, routePrefix} = {}) {
|
|
|
117
126
|
|
|
118
127
|
// if credential templates are specified, then `zcaps` MUST include at
|
|
119
128
|
// least `issue`
|
|
120
|
-
const {credentialTemplates = [], zcaps = {}} = config;
|
|
121
|
-
if(credentialTemplates.length > 0
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const {credentialTemplates = [], zcaps = {}, issuerInstances} = config;
|
|
130
|
+
if(credentialTemplates.length > 0) {
|
|
131
|
+
if(!(zcaps.issue || issuerInstances)) {
|
|
132
|
+
throw new BedrockError(
|
|
133
|
+
'A capability to issue credentials is required when credential ' +
|
|
134
|
+
'templates are provided.', {
|
|
135
|
+
name: 'DataError',
|
|
136
|
+
details: {
|
|
137
|
+
httpStatusCode: 400,
|
|
138
|
+
public: true
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// ensure that, if `issuerInstances` is given, that every zcap
|
|
143
|
+
// referenced in each issuer instance config's `zcapReferenceIds` can
|
|
144
|
+
// be found in `zcaps`
|
|
145
|
+
if(issuerInstances) {
|
|
146
|
+
if(!issuerInstances.every(
|
|
147
|
+
instance => !!zcaps[instance.zcapReferenceIds.issue])) {
|
|
148
|
+
throw new BedrockError(
|
|
149
|
+
'An issuer instance configuration zcap reference ID is not ' +
|
|
150
|
+
'present in "config.zcaps".', {
|
|
151
|
+
name: 'DataError',
|
|
152
|
+
details: {
|
|
153
|
+
httpStatusCode: 400,
|
|
154
|
+
public: true
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
// if `steps` are specified, then `initialStep` MUST be included
|
package/lib/issue.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
evaluateTemplate,
|
|
6
|
+
getWorkflowIssuerInstances,
|
|
7
|
+
getZcapClient
|
|
8
|
+
} from './helpers.js';
|
|
5
9
|
import {createPresentation} from '@digitalbazaar/vc';
|
|
6
10
|
|
|
7
|
-
export async function issue({
|
|
11
|
+
export async function issue({
|
|
12
|
+
workflow, exchange, format = 'application/vc'
|
|
13
|
+
} = {}) {
|
|
8
14
|
// use any templates from workflow and variables from exchange to produce
|
|
9
15
|
// credentials to be issued; issue via the configured issuer instance
|
|
10
16
|
const verifiableCredential = [];
|
|
11
17
|
const {credentialTemplates = []} = workflow;
|
|
12
18
|
if(!credentialTemplates || credentialTemplates.length === 0) {
|
|
13
19
|
// nothing to issue
|
|
14
|
-
return {};
|
|
20
|
+
return {response: {}};
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
// evaluate template
|
|
18
|
-
const
|
|
24
|
+
const issueRequests = await Promise.all(credentialTemplates.map(
|
|
19
25
|
typedTemplate => evaluateTemplate({workflow, exchange, typedTemplate})));
|
|
20
26
|
// issue all VCs
|
|
21
|
-
const vcs = await _issue({workflow,
|
|
27
|
+
const vcs = await _issue({workflow, issueRequests, format});
|
|
22
28
|
verifiableCredential.push(...vcs);
|
|
23
29
|
|
|
24
30
|
// generate VP to return VCs
|
|
@@ -29,15 +35,16 @@ export async function issue({workflow, exchange} = {}) {
|
|
|
29
35
|
if(verifiableCredential.length > 0) {
|
|
30
36
|
verifiablePresentation.verifiableCredential = verifiableCredential;
|
|
31
37
|
}
|
|
32
|
-
return {verifiablePresentation};
|
|
38
|
+
return {response: {verifiablePresentation}, format};
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
async function _issue({workflow,
|
|
41
|
+
async function _issue({workflow, issueRequests, format} = {}) {
|
|
36
42
|
// create zcap client for issuing VCs
|
|
37
43
|
const {zcapClient, zcaps} = await getZcapClient({workflow});
|
|
38
44
|
|
|
39
|
-
//
|
|
40
|
-
const capability = zcaps
|
|
45
|
+
// get the zcap to use for the issue requests
|
|
46
|
+
const capability = _getIssueZcap({workflow, zcaps, format});
|
|
47
|
+
|
|
41
48
|
// specify URL to `/credentials/issue` to handle case that capability
|
|
42
49
|
// is not specific to it
|
|
43
50
|
let url = capability.invocationTarget;
|
|
@@ -45,15 +52,32 @@ async function _issue({workflow, credentialRequests} = {}) {
|
|
|
45
52
|
url += capability.invocationTarget.endsWith('/credentials') ?
|
|
46
53
|
'/issue' : '/credentials/issue';
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
|
|
56
|
+
const issuedVCs = [];
|
|
57
|
+
|
|
58
|
+
// issue VCs in parallel
|
|
59
|
+
await Promise.all(issueRequests.map(async issueRequest => {
|
|
60
|
+
/* Note: Issue request formats can be any one of these:
|
|
61
|
+
|
|
62
|
+
1. `{credential, options?}`
|
|
63
|
+
2. `credential`
|
|
64
|
+
|
|
65
|
+
Normalize issue requests that use the full VC API issue request and those
|
|
66
|
+
that return only the `credential` param directly. */
|
|
67
|
+
const json = issueRequest.credential ?
|
|
68
|
+
issueRequest : {credential: issueRequest};
|
|
69
|
+
const {
|
|
70
|
+
data: {verifiableCredential}
|
|
71
|
+
} = await zcapClient.write({url, capability, json});
|
|
72
|
+
issuedVCs.push(verifiableCredential);
|
|
53
73
|
}));
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
return issuedVCs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _getIssueZcap({workflow, zcaps, format}) {
|
|
79
|
+
const issuerInstances = getWorkflowIssuerInstances({workflow});
|
|
80
|
+
const {zcapReferenceIds: {issue: issueRefId}} = issuerInstances.find(
|
|
81
|
+
({supportedFormats}) => supportedFormats.includes(format));
|
|
82
|
+
return zcaps[issueRefId];
|
|
59
83
|
}
|
package/lib/openId.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as exchanges from './exchanges.js';
|
|
|
6
6
|
import {
|
|
7
7
|
compile, schemas, createValidateMiddleware as validate
|
|
8
8
|
} from '@bedrock/validation';
|
|
9
|
+
import {evaluateTemplate, getWorkflowIssuerInstances} from './helpers.js';
|
|
9
10
|
import {importJWK, SignJWT} from 'jose';
|
|
10
11
|
import {
|
|
11
12
|
openIdAuthorizationResponseBody,
|
|
@@ -19,7 +20,6 @@ import {asyncHandler} from '@bedrock/express';
|
|
|
19
20
|
import bodyParser from 'body-parser';
|
|
20
21
|
import {checkAccessToken} from '@bedrock/oauth2-verifier';
|
|
21
22
|
import cors from 'cors';
|
|
22
|
-
import {evaluateTemplate} from './helpers.js';
|
|
23
23
|
import {issue} from './issue.js';
|
|
24
24
|
import {klona} from 'klona';
|
|
25
25
|
import {oid4vp} from '@digitalbazaar/oid4-client';
|
|
@@ -254,6 +254,7 @@ export async function createRoutes({
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
*/
|
|
257
|
+
|
|
257
258
|
const result = await _processCredentialRequests(
|
|
258
259
|
{req, res, isBatchRequest: false});
|
|
259
260
|
if(!result) {
|
|
@@ -261,14 +262,27 @@ export async function createRoutes({
|
|
|
261
262
|
return;
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
|
|
265
|
+
/* Note: The `/credential` route only supports sending a single VC;
|
|
266
|
+
assume here that this workflow is configured for a single VC and an
|
|
267
|
+
error code would have been sent to the client to use the batch
|
|
268
|
+
endpoint if there was more than one VC to deliver. */
|
|
269
|
+
const {response, format} = result;
|
|
270
|
+
const {verifiablePresentation: {verifiableCredential: [vc]}} = response;
|
|
271
|
+
|
|
272
|
+
// parse any enveloped VC
|
|
273
|
+
let credential;
|
|
274
|
+
if(vc.type === 'EnvelopedVerifiableCredential' &&
|
|
275
|
+
vc.id?.startsWith('data:application/jwt,')) {
|
|
276
|
+
credential = vc.id.slice('data:application/jwt,'.length);
|
|
277
|
+
} else {
|
|
278
|
+
credential = vc;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// send OID4VCI response
|
|
265
282
|
res.json({
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
error code would have been sent to the client to use the batch
|
|
270
|
-
endpoint if there was more than one VC to deliver. */
|
|
271
|
-
credential: result.verifiablePresentation.verifiableCredential[0]
|
|
283
|
+
// FIXME: this doesn't seem to be in the spec anymore (draft 14+)...
|
|
284
|
+
format,
|
|
285
|
+
credential
|
|
272
286
|
});
|
|
273
287
|
}));
|
|
274
288
|
|
|
@@ -317,8 +331,19 @@ export async function createRoutes({
|
|
|
317
331
|
}
|
|
318
332
|
|
|
319
333
|
// send VCs
|
|
320
|
-
const
|
|
321
|
-
|
|
334
|
+
const {response: {verifiablePresentation}, format} = result;
|
|
335
|
+
// FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
|
|
336
|
+
const responses = verifiablePresentation.verifiableCredential.map(vc => {
|
|
337
|
+
// parse any enveloped VC
|
|
338
|
+
let credential;
|
|
339
|
+
if(vc.type === 'EnvelopedVerifiableCredential' &&
|
|
340
|
+
vc.id?.startsWith('data:application/jwt,')) {
|
|
341
|
+
credential = vc.id.slice('data:application/jwt,'.length);
|
|
342
|
+
} else {
|
|
343
|
+
credential = vc;
|
|
344
|
+
}
|
|
345
|
+
return {format, credential};
|
|
346
|
+
});
|
|
322
347
|
res.json({credential_responses: responses});
|
|
323
348
|
}));
|
|
324
349
|
|
|
@@ -528,11 +553,12 @@ async function _processCredentialRequests({req, res, isBatchRequest}) {
|
|
|
528
553
|
}
|
|
529
554
|
|
|
530
555
|
// before asserting, normalize credential requests to use `type` instead of
|
|
531
|
-
// `types`; this is to allow for OID4VCI draft
|
|
556
|
+
// `types`; this is to allow for OID4VCI draft implementers that followed
|
|
532
557
|
// the non-normative examples
|
|
533
558
|
_normalizeCredentialDefinitionTypes({credentialRequests});
|
|
534
|
-
_assertCredentialRequests(
|
|
535
|
-
|
|
559
|
+
const {format} = _assertCredentialRequests({
|
|
560
|
+
workflow, credentialRequests, expectedCredentialRequests
|
|
561
|
+
});
|
|
536
562
|
|
|
537
563
|
// process exchange step if present
|
|
538
564
|
const currentStep = exchange.step;
|
|
@@ -617,7 +643,7 @@ async function _processCredentialRequests({req, res, isBatchRequest}) {
|
|
|
617
643
|
// replay attack detected) after exchange has been marked complete
|
|
618
644
|
|
|
619
645
|
// issue VCs
|
|
620
|
-
return issue({workflow, exchange});
|
|
646
|
+
return issue({workflow, exchange, format});
|
|
621
647
|
}
|
|
622
648
|
|
|
623
649
|
async function _requestDidProof({res, exchangeRecord}) {
|
|
@@ -813,24 +839,66 @@ async function _getAuthorizationRequest({req}) {
|
|
|
813
839
|
}
|
|
814
840
|
|
|
815
841
|
function _assertCredentialRequests({
|
|
816
|
-
credentialRequests, expectedCredentialRequests
|
|
842
|
+
workflow, credentialRequests, expectedCredentialRequests
|
|
817
843
|
}) {
|
|
844
|
+
// ensure that every credential request is for the same format
|
|
845
|
+
/* credential requests look like:
|
|
846
|
+
{
|
|
847
|
+
format: 'ldp_vc',
|
|
848
|
+
credential_definition: { '@context': [Array], type: [Array] }
|
|
849
|
+
}
|
|
850
|
+
*/
|
|
851
|
+
let sharedFormat;
|
|
852
|
+
if(!credentialRequests.every(({format}) => {
|
|
853
|
+
if(sharedFormat === undefined) {
|
|
854
|
+
sharedFormat = format;
|
|
855
|
+
}
|
|
856
|
+
return sharedFormat === format;
|
|
857
|
+
})) {
|
|
858
|
+
throw new BedrockError(
|
|
859
|
+
'Credential requests must all use the same format in this workflow.', {
|
|
860
|
+
name: 'DataError',
|
|
861
|
+
details: {httpStatusCode: 400, public: true}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// get all supported formats from available issuer instances; for simple
|
|
866
|
+
// workflow configs, a single issuer instance is used with only
|
|
867
|
+
// ensure that every credential request uses a format supported by
|
|
868
|
+
// issuer instances
|
|
869
|
+
const supportedFormats = new Set();
|
|
870
|
+
const issuerInstances = getWorkflowIssuerInstances({workflow});
|
|
871
|
+
issuerInstances.forEach(
|
|
872
|
+
instance => instance.supportedFormats.forEach(
|
|
873
|
+
supportedFormats.add, supportedFormats));
|
|
874
|
+
if(!supportedFormats.has(sharedFormat)) {
|
|
875
|
+
throw new BedrockError(
|
|
876
|
+
`Credential request format "${sharedFormat}" is not supported ` +
|
|
877
|
+
'by this workflow.', {
|
|
878
|
+
name: 'DataError',
|
|
879
|
+
details: {httpStatusCode: 400, public: true}
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
818
883
|
// ensure every credential request matches against an expected one and none
|
|
819
|
-
// are missing
|
|
884
|
+
// are missing; `expectedCredentialRequests` formats are ignored based on the
|
|
885
|
+
// issuer instance supported formats and have already been checked
|
|
820
886
|
if(!(credentialRequests.length === expectedCredentialRequests.length &&
|
|
821
887
|
credentialRequests.every(cr => expectedCredentialRequests.some(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
888
|
+
expected => _matchCredentialRequest(expected, cr))))) {
|
|
889
|
+
throw new BedrockError(
|
|
890
|
+
'Unexpected credential request.', {
|
|
891
|
+
name: 'DataError',
|
|
892
|
+
details: {httpStatusCode: 400, public: true}
|
|
893
|
+
});
|
|
825
894
|
}
|
|
895
|
+
|
|
896
|
+
return {format: sharedFormat};
|
|
826
897
|
}
|
|
827
898
|
|
|
828
|
-
function _matchCredentialRequest(
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
const {credential_definition: {'@context': c1, type: t1}} = a;
|
|
833
|
-
const {credential_definition: {'@context': c2, type: t2}} = b;
|
|
899
|
+
function _matchCredentialRequest(expected, cr) {
|
|
900
|
+
const {credential_definition: {'@context': c1, type: t1}} = expected;
|
|
901
|
+
const {credential_definition: {'@context': c2, type: t2}} = cr;
|
|
834
902
|
// contexts must match exact order but types can have different order
|
|
835
903
|
return (c1.length === c2.length && t1.length === t2.length &&
|
|
836
904
|
c1.every((c, i) => c === c2[i]) && t1.every(t => t2.some(x => t === x)));
|
package/lib/vcapi.js
CHANGED
|
@@ -156,15 +156,15 @@ export async function processExchange({req, res, workflow, exchange}) {
|
|
|
156
156
|
// FIXME: decide what the best recovery path is if delivery fails (but no
|
|
157
157
|
// replay attack detected) after exchange has been marked complete
|
|
158
158
|
|
|
159
|
-
// issue any VCs; may return an empty
|
|
159
|
+
// issue any VCs; may return an empty response if the step defines no
|
|
160
160
|
// VCs to issue
|
|
161
|
-
const
|
|
161
|
+
const {response} = await issue({workflow, exchange});
|
|
162
162
|
|
|
163
163
|
// if last `step` has a redirect URL, include it in the response
|
|
164
164
|
if(step?.redirectUrl) {
|
|
165
|
-
|
|
165
|
+
response.redirectUrl = step.redirectUrl;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// send
|
|
169
|
-
res.json(
|
|
168
|
+
// send response
|
|
169
|
+
res.json(response);
|
|
170
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
+
import {MAX_ISSUER_INSTANCES} from '../lib/constants.js';
|
|
4
5
|
import {schemas} from '@bedrock/validation';
|
|
5
6
|
|
|
6
7
|
const credentialDefinition = {
|
|
@@ -34,6 +35,19 @@ const credentialDefinition = {
|
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
const expectedCredentialRequest = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
required: ['credential_definition'],
|
|
42
|
+
properties: {
|
|
43
|
+
credential_definition: credentialDefinition,
|
|
44
|
+
format: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: ['ldp_vc', 'jwt_vc_json-ld']
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
37
51
|
const openIdExchangeOptions = {
|
|
38
52
|
title: 'OpenID Exchange options',
|
|
39
53
|
type: 'object',
|
|
@@ -44,18 +58,7 @@ const openIdExchangeOptions = {
|
|
|
44
58
|
title: 'OpenID Expected Credential Requests',
|
|
45
59
|
type: 'array',
|
|
46
60
|
minItems: 1,
|
|
47
|
-
items:
|
|
48
|
-
type: 'object',
|
|
49
|
-
additionalProperties: false,
|
|
50
|
-
required: ['credential_definition', 'format'],
|
|
51
|
-
properties: {
|
|
52
|
-
credential_definition: credentialDefinition,
|
|
53
|
-
format: {
|
|
54
|
-
type: 'string',
|
|
55
|
-
enum: ['ldp_vc']
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
61
|
+
items: expectedCredentialRequest
|
|
59
62
|
},
|
|
60
63
|
preAuthorizedCode: {
|
|
61
64
|
type: 'string'
|
|
@@ -123,6 +126,9 @@ const typedTemplate = {
|
|
|
123
126
|
required: ['type', 'template'],
|
|
124
127
|
additionalProperties: false,
|
|
125
128
|
properties: {
|
|
129
|
+
id: {
|
|
130
|
+
type: 'string'
|
|
131
|
+
},
|
|
126
132
|
type: {
|
|
127
133
|
type: 'string',
|
|
128
134
|
enum: ['jsonata']
|
|
@@ -140,6 +146,52 @@ export const credentialTemplates = {
|
|
|
140
146
|
items: typedTemplate
|
|
141
147
|
};
|
|
142
148
|
|
|
149
|
+
// to be updated in specific locations with `properties` and `required`
|
|
150
|
+
const zcapReferenceIds = {
|
|
151
|
+
title: 'Authorization Capability Reference IDs',
|
|
152
|
+
type: 'object',
|
|
153
|
+
additionalProperties: false
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const vcFormats = {
|
|
157
|
+
title: 'Verifiable Credential formats',
|
|
158
|
+
type: 'array',
|
|
159
|
+
minItems: 1,
|
|
160
|
+
items: {
|
|
161
|
+
type: 'string'
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const issuerInstance = {
|
|
166
|
+
title: 'Issuer Instance',
|
|
167
|
+
type: 'object',
|
|
168
|
+
required: ['zcapReferenceIds'],
|
|
169
|
+
additionalProperties: false,
|
|
170
|
+
properties: {
|
|
171
|
+
id: {
|
|
172
|
+
type: 'string'
|
|
173
|
+
},
|
|
174
|
+
supportedFormats: vcFormats,
|
|
175
|
+
zcapReferenceIds: {
|
|
176
|
+
...zcapReferenceIds,
|
|
177
|
+
required: ['issue'],
|
|
178
|
+
properties: {
|
|
179
|
+
issue: {
|
|
180
|
+
type: 'string'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const issuerInstances = {
|
|
188
|
+
title: 'Issuer Instances',
|
|
189
|
+
type: 'array',
|
|
190
|
+
minItems: 1,
|
|
191
|
+
maxItems: MAX_ISSUER_INSTANCES,
|
|
192
|
+
items: issuerInstance
|
|
193
|
+
};
|
|
194
|
+
|
|
143
195
|
const step = {
|
|
144
196
|
title: 'Exchange Step',
|
|
145
197
|
type: 'object',
|
|
@@ -276,7 +328,7 @@ const openIdCredentialRequest = {
|
|
|
276
328
|
credential_definition: credentialDefinition,
|
|
277
329
|
format: {
|
|
278
330
|
type: 'string',
|
|
279
|
-
enum: ['ldp_vc']
|
|
331
|
+
enum: ['ldp_vc', 'jwt_vc_json-ld']
|
|
280
332
|
},
|
|
281
333
|
did: {
|
|
282
334
|
type: 'string'
|