@bedrock/vc-delivery 6.4.1 → 6.6.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/exchanges.js +49 -7
- package/lib/helpers.js +12 -2
- package/lib/oid4/oid4vp.js +20 -4
- package/package.json +1 -1
- package/schemas/bedrock-vc-workflow.js +17 -3
package/lib/exchanges.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
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 database from '@bedrock/mongodb';
|
|
@@ -35,6 +35,8 @@ const LAST_ERROR_UPDATE_CONSTRAINTS = {
|
|
|
35
35
|
updateTimeLimit: 1000
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
const MONGODB_ILLEGAL_KEY_CHAR_REGEX = /[%$.]/;
|
|
39
|
+
|
|
38
40
|
bedrock.events.on('bedrock-mongodb.ready', async () => {
|
|
39
41
|
await database.openCollections([COLLECTION_NAME]);
|
|
40
42
|
|
|
@@ -110,14 +112,14 @@ export async function insert({workflowId, exchange}) {
|
|
|
110
112
|
// backwards compatibility: enable existing systems to find record
|
|
111
113
|
localExchangerId: localWorkflowId,
|
|
112
114
|
meta,
|
|
113
|
-
exchange
|
|
115
|
+
exchange: _encodeVariables({exchange})
|
|
114
116
|
};
|
|
115
117
|
|
|
116
118
|
// insert the exchange and get the updated record
|
|
117
119
|
try {
|
|
118
120
|
const collection = database.collections[COLLECTION_NAME];
|
|
119
|
-
|
|
120
|
-
return
|
|
121
|
+
await collection.insertOne(record);
|
|
122
|
+
return record;
|
|
121
123
|
} catch(e) {
|
|
122
124
|
if(!database.isDuplicateError(e)) {
|
|
123
125
|
throw e;
|
|
@@ -199,6 +201,8 @@ export async function get({
|
|
|
199
201
|
});
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
record.exchange = _decodeVariables({exchange: record.exchange});
|
|
205
|
+
|
|
202
206
|
// backwards compatibility; initialize `sequence`
|
|
203
207
|
if(record.exchange.sequence === undefined) {
|
|
204
208
|
const query = {
|
|
@@ -237,6 +241,9 @@ export async function update({workflowId, exchange, explain = false} = {}) {
|
|
|
237
241
|
assert.object(exchange, 'exchange');
|
|
238
242
|
const {id} = exchange;
|
|
239
243
|
|
|
244
|
+
// encode variable content for storage in mongoDB
|
|
245
|
+
exchange = _encodeVariables({exchange});
|
|
246
|
+
|
|
240
247
|
// build update
|
|
241
248
|
const update = _buildUpdate({exchange, complete: false});
|
|
242
249
|
|
|
@@ -566,13 +573,13 @@ function _buildUpdate({exchange, complete}) {
|
|
|
566
573
|
$set: {'exchange.state': exchange.state, 'meta.updated': now},
|
|
567
574
|
$unset: {}
|
|
568
575
|
};
|
|
569
|
-
if(complete) {
|
|
570
|
-
// exchange complete, only update results
|
|
576
|
+
if(complete && typeof exchange.variables !== 'string') {
|
|
577
|
+
// exchange complete and variables not encoded, so only update results
|
|
571
578
|
if(exchange.variables?.results) {
|
|
572
579
|
update.$set['exchange.variables.results'] = exchange.variables.results;
|
|
573
580
|
}
|
|
574
581
|
} else {
|
|
575
|
-
// exchange not complete, update all variables
|
|
582
|
+
// exchange not complete or variables are encoded, so update all variables
|
|
576
583
|
if(exchange.variables) {
|
|
577
584
|
update.$set['exchange.variables'] = exchange.variables;
|
|
578
585
|
}
|
|
@@ -600,6 +607,41 @@ function _buildUpdate({exchange, complete}) {
|
|
|
600
607
|
return update;
|
|
601
608
|
}
|
|
602
609
|
|
|
610
|
+
function _encodeVariables({exchange}) {
|
|
611
|
+
// if any JSON object any variable uses a character that is not legal in
|
|
612
|
+
// a JSON key in mongoDB then stringify all the variables
|
|
613
|
+
if(_hasIllegalMongoDBKeyChar(exchange.variables)) {
|
|
614
|
+
return {...exchange, variables: JSON.stringify(exchange.variables)};
|
|
615
|
+
}
|
|
616
|
+
return exchange;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function _decodeVariables({exchange}) {
|
|
620
|
+
if(typeof exchange.variables === 'string') {
|
|
621
|
+
return {...exchange, variables: JSON.parse(exchange.variables)};
|
|
622
|
+
}
|
|
623
|
+
return exchange;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function _hasIllegalMongoDBKeyChar(value) {
|
|
627
|
+
if(Array.isArray(value)) {
|
|
628
|
+
for(const e of value) {
|
|
629
|
+
if(_hasIllegalMongoDBKeyChar(e)) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} else if(value && typeof value === 'object') {
|
|
634
|
+
const keys = Object.keys(value);
|
|
635
|
+
for(const key of keys) {
|
|
636
|
+
if(MONGODB_ILLEGAL_KEY_CHAR_REGEX.test(key) ||
|
|
637
|
+
_hasIllegalMongoDBKeyChar(value[key])) {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
603
645
|
/**
|
|
604
646
|
* An object containing information on the query plan.
|
|
605
647
|
*
|
package/lib/helpers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
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 vcjwt from './vcjwt.js';
|
|
@@ -18,6 +18,16 @@ const ALLOWED_ERROR_KEYS = [
|
|
|
18
18
|
'status'
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
+
const JWT_FORMAT_ALIASES = new Set([
|
|
22
|
+
'application/jwt',
|
|
23
|
+
'application/vc+jwt',
|
|
24
|
+
'application/vp+jwt',
|
|
25
|
+
'jwt_vp',
|
|
26
|
+
'jwt_vp_json',
|
|
27
|
+
'jwt_vc_json-ld',
|
|
28
|
+
'jwt_vc_json'
|
|
29
|
+
]);
|
|
30
|
+
|
|
21
31
|
export async function evaluateTemplate({
|
|
22
32
|
workflow, exchange, typedTemplate, variables
|
|
23
33
|
} = {}) {
|
|
@@ -243,7 +253,7 @@ function _getEnvelope({envelope, format}) {
|
|
|
243
253
|
const isString = typeof envelope === 'string';
|
|
244
254
|
if(isString) {
|
|
245
255
|
// supported formats
|
|
246
|
-
if(format
|
|
256
|
+
if(JWT_FORMAT_ALIASES.has(format)) {
|
|
247
257
|
format = 'application/jwt';
|
|
248
258
|
}
|
|
249
259
|
} else {
|
package/lib/oid4/oid4vp.js
CHANGED
|
@@ -289,9 +289,20 @@ function _createClientMetaData() {
|
|
|
289
289
|
// return default supported `vp_formats`
|
|
290
290
|
return {
|
|
291
291
|
vp_formats: {
|
|
292
|
+
// support both aliases `jwt_vp` and `jwt_vp_json`
|
|
292
293
|
jwt_vp: {
|
|
293
294
|
alg: ['EdDSA', 'Ed25519', 'ES256', 'ES384']
|
|
294
295
|
},
|
|
296
|
+
jwt_vp_json: {
|
|
297
|
+
alg: ['EdDSA', 'Ed25519', 'ES256', 'ES384']
|
|
298
|
+
},
|
|
299
|
+
di_vp: {
|
|
300
|
+
proof_type: [
|
|
301
|
+
'ecdsa-rdfc-2019',
|
|
302
|
+
'eddsa-rdfc-2022',
|
|
303
|
+
'Ed25519Signature2020'
|
|
304
|
+
]
|
|
305
|
+
},
|
|
295
306
|
ldp_vp: {
|
|
296
307
|
proof_type: [
|
|
297
308
|
'ecdsa-rdfc-2019',
|
|
@@ -308,7 +319,7 @@ async function _parseAuthorizationResponse({req}) {
|
|
|
308
319
|
const {vp_token, presentation_submission} = req.body;
|
|
309
320
|
|
|
310
321
|
// JSON parse and validate `vp_token` and `presentation_submission`
|
|
311
|
-
let presentation = _jsonParse(vp_token, 'vp_token');
|
|
322
|
+
let presentation = _jsonParse(vp_token, 'vp_token', true);
|
|
312
323
|
const presentationSubmission = _jsonParse(
|
|
313
324
|
presentation_submission, 'presentation_submission');
|
|
314
325
|
_validate(VALIDATORS.presentationSubmission, presentationSubmission);
|
|
@@ -319,8 +330,8 @@ async function _parseAuthorizationResponse({req}) {
|
|
|
319
330
|
envelope: raw, presentation: contents, format
|
|
320
331
|
} = await unenvelopePresentation({
|
|
321
332
|
envelopedPresentation: presentation,
|
|
322
|
-
// FIXME: check presentationSubmission for VP format
|
|
323
|
-
format: '
|
|
333
|
+
// FIXME: check `presentationSubmission` for VP format
|
|
334
|
+
format: 'application/jwt'
|
|
324
335
|
});
|
|
325
336
|
_validate(VALIDATORS.presentation, contents);
|
|
326
337
|
presentation = {
|
|
@@ -336,10 +347,15 @@ async function _parseAuthorizationResponse({req}) {
|
|
|
336
347
|
return {presentation, envelope, presentationSubmission};
|
|
337
348
|
}
|
|
338
349
|
|
|
339
|
-
function _jsonParse(x, name) {
|
|
350
|
+
function _jsonParse(x, name, allowJWT = false) {
|
|
340
351
|
try {
|
|
341
352
|
return JSON.parse(x);
|
|
342
353
|
} catch(cause) {
|
|
354
|
+
// presume the string is a non-JSON encoded JWT and let subsequent
|
|
355
|
+
// checking handle it (`ey` is base64url-encoded `{`)
|
|
356
|
+
if(allowJWT && x?.startsWith('ey')) {
|
|
357
|
+
return x;
|
|
358
|
+
}
|
|
343
359
|
throw new BedrockError(`Could not parse "${name}".`, {
|
|
344
360
|
name: 'DataError',
|
|
345
361
|
details: {httpStatusCode: 400, public: true},
|
package/package.json
CHANGED
|
@@ -181,7 +181,7 @@ const expectedCredentialRequest = {
|
|
|
181
181
|
credential_definition: credentialDefinition,
|
|
182
182
|
format: {
|
|
183
183
|
type: 'string',
|
|
184
|
-
enum: ['ldp_vc', 'jwt_vc_json-ld']
|
|
184
|
+
enum: ['di_vc', 'ldp_vc', 'jwt_vc_json-ld', 'jwt_vc_json']
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
};
|
|
@@ -509,7 +509,7 @@ const openIdCredentialRequest = {
|
|
|
509
509
|
credential_definition: credentialDefinition,
|
|
510
510
|
format: {
|
|
511
511
|
type: 'string',
|
|
512
|
-
enum: ['ldp_vc', 'jwt_vc_json-ld']
|
|
512
|
+
enum: ['di_vc', 'ldp_vc', 'jwt_vc_json-ld', 'jwt_vc_json']
|
|
513
513
|
},
|
|
514
514
|
did: {
|
|
515
515
|
type: 'string'
|
|
@@ -631,7 +631,21 @@ export function openIdAuthorizationResponseBody() {
|
|
|
631
631
|
presentation_submission: {
|
|
632
632
|
type: 'string'
|
|
633
633
|
},
|
|
634
|
-
// is a JSON string in the x-www-form-urlencoded body
|
|
634
|
+
// is a JSON-encoded string or object in the x-www-form-urlencoded body
|
|
635
|
+
/* Note: This can also be a simple base64url string for
|
|
636
|
+
backwards/forwards compatibility. While submitting VPs directly as
|
|
637
|
+
JSON objects has never changed in the OID4* specs, submitting VPs that
|
|
638
|
+
are wrapped in some envelope that is expressed as a string (e.g., a JWT)
|
|
639
|
+
has changed back and forth throughout the draft history. Sometimes these
|
|
640
|
+
vp_tokens are required to be JSON-encoded strings other times non-JSON
|
|
641
|
+
strings, i.e., no "extra/JSON quotes" around the string value inside the
|
|
642
|
+
x-www-form-urlencoded field value delimiting quotes. For example,
|
|
643
|
+
both of these:
|
|
644
|
+
|
|
645
|
+
`...&vp_token="non-string JSON"`
|
|
646
|
+
`...&vp_token="\"JSON string\""`
|
|
647
|
+
|
|
648
|
+
are accepted for these reasons. */
|
|
635
649
|
vp_token: {
|
|
636
650
|
type: 'string'
|
|
637
651
|
},
|