@bedrock/vc-delivery 5.3.5 → 5.5.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/oid4/http.js +122 -61
- package/lib/oid4/oid4vci.js +15 -5
- package/package.json +1 -1
package/lib/oid4/http.js
CHANGED
|
@@ -156,8 +156,13 @@ export async function createRoutes({
|
|
|
156
156
|
getConfigMiddleware,
|
|
157
157
|
getExchange,
|
|
158
158
|
asyncHandler(async (req, res) => {
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
let result;
|
|
160
|
+
try {
|
|
161
|
+
result = await oid4vci.processAccessTokenRequest({req, res});
|
|
162
|
+
} catch(error) {
|
|
163
|
+
return _sendOID4Error({res, error});
|
|
164
|
+
}
|
|
165
|
+
res.json(result);
|
|
161
166
|
}));
|
|
162
167
|
|
|
163
168
|
// a credential delivery server endpoint
|
|
@@ -195,36 +200,46 @@ export async function createRoutes({
|
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
202
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
let result;
|
|
204
|
+
try {
|
|
205
|
+
result = await oid4vci.processCredentialRequests({
|
|
206
|
+
req, res, isBatchRequest: false
|
|
207
|
+
});
|
|
208
|
+
if(!result) {
|
|
209
|
+
// DID proof request response sent
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
205
212
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
// send VC(s)
|
|
214
|
+
const {
|
|
215
|
+
response: {verifiablePresentation: {verifiableCredential}},
|
|
216
|
+
format
|
|
217
|
+
} = result;
|
|
218
|
+
// FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
|
|
219
|
+
const credentials = verifiableCredential.map(vc => {
|
|
220
|
+
// parse any enveloped VC
|
|
221
|
+
let credential;
|
|
222
|
+
if(vc.type === 'EnvelopedVerifiableCredential' &&
|
|
223
|
+
vc.id?.startsWith('data:application/jwt,')) {
|
|
224
|
+
credential = vc.id.slice('data:application/jwt,'.length);
|
|
225
|
+
} else {
|
|
226
|
+
credential = vc;
|
|
227
|
+
}
|
|
228
|
+
return credential;
|
|
229
|
+
});
|
|
212
230
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
credential = vc.id.slice('data:application/jwt,'.length);
|
|
218
|
-
} else {
|
|
219
|
-
credential = vc;
|
|
220
|
-
}
|
|
231
|
+
/* Note: The `/credential` route only supports sending VCs of the same
|
|
232
|
+
type, but there can be more than one of them. The above `isBatchRequest`
|
|
233
|
+
check will ensure that the workflow used here only allows a single
|
|
234
|
+
credential request, indicating a single type. */
|
|
221
235
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
236
|
+
// send OID4VCI response
|
|
237
|
+
result = credentials.length === 1 ?
|
|
238
|
+
{format, credential: credentials[0]} : {format, credentials};
|
|
239
|
+
} catch(error) {
|
|
240
|
+
return _sendOID4Error({res, error});
|
|
241
|
+
}
|
|
242
|
+
res.json(result);
|
|
228
243
|
}));
|
|
229
244
|
|
|
230
245
|
// a credential delivery server endpoint
|
|
@@ -235,8 +250,13 @@ export async function createRoutes({
|
|
|
235
250
|
getConfigMiddleware,
|
|
236
251
|
getExchange,
|
|
237
252
|
asyncHandler(async (req, res) => {
|
|
238
|
-
|
|
239
|
-
|
|
253
|
+
let result;
|
|
254
|
+
try {
|
|
255
|
+
result = await oid4vci.getCredentialOffer({req});
|
|
256
|
+
} catch(error) {
|
|
257
|
+
return _sendOID4Error({res, error});
|
|
258
|
+
}
|
|
259
|
+
res.json(result);
|
|
240
260
|
}));
|
|
241
261
|
|
|
242
262
|
// a batch credential delivery server endpoint
|
|
@@ -276,29 +296,37 @@ export async function createRoutes({
|
|
|
276
296
|
}]
|
|
277
297
|
}
|
|
278
298
|
*/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// send VCs
|
|
288
|
-
const {response: {verifiablePresentation}, format} = result;
|
|
289
|
-
// FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
|
|
290
|
-
const responses = verifiablePresentation.verifiableCredential.map(vc => {
|
|
291
|
-
// parse any enveloped VC
|
|
292
|
-
let credential;
|
|
293
|
-
if(vc.type === 'EnvelopedVerifiableCredential' &&
|
|
294
|
-
vc.id?.startsWith('data:application/jwt,')) {
|
|
295
|
-
credential = vc.id.slice('data:application/jwt,'.length);
|
|
296
|
-
} else {
|
|
297
|
-
credential = vc;
|
|
299
|
+
let result;
|
|
300
|
+
try {
|
|
301
|
+
result = await oid4vci.processCredentialRequests({
|
|
302
|
+
req, res, isBatchRequest: true
|
|
303
|
+
});
|
|
304
|
+
if(!result) {
|
|
305
|
+
// DID proof request response sent
|
|
306
|
+
return;
|
|
298
307
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
308
|
+
|
|
309
|
+
// send VCs
|
|
310
|
+
const {
|
|
311
|
+
response: {verifiablePresentation: {verifiableCredential}},
|
|
312
|
+
format
|
|
313
|
+
} = result;
|
|
314
|
+
// FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
|
|
315
|
+
result = verifiableCredential.map(vc => {
|
|
316
|
+
// parse any enveloped VC
|
|
317
|
+
let credential;
|
|
318
|
+
if(vc.type === 'EnvelopedVerifiableCredential' &&
|
|
319
|
+
vc.id?.startsWith('data:application/jwt,')) {
|
|
320
|
+
credential = vc.id.slice('data:application/jwt,'.length);
|
|
321
|
+
} else {
|
|
322
|
+
credential = vc;
|
|
323
|
+
}
|
|
324
|
+
return {format, credential};
|
|
325
|
+
});
|
|
326
|
+
} catch(error) {
|
|
327
|
+
return _sendOID4Error({res, error});
|
|
328
|
+
}
|
|
329
|
+
res.json({credential_responses: result});
|
|
302
330
|
}));
|
|
303
331
|
|
|
304
332
|
// an OID4VP verifier endpoint
|
|
@@ -310,13 +338,18 @@ export async function createRoutes({
|
|
|
310
338
|
getConfigMiddleware,
|
|
311
339
|
getExchange,
|
|
312
340
|
asyncHandler(async (req, res) => {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
341
|
+
let result;
|
|
342
|
+
try {
|
|
343
|
+
const {
|
|
344
|
+
authorizationRequest
|
|
345
|
+
} = await oid4vp.getAuthorizationRequest({req});
|
|
346
|
+
// construct and send authz request as unsecured JWT
|
|
347
|
+
result = new UnsecuredJWT(authorizationRequest).encode();
|
|
348
|
+
res.set('content-type', 'application/oauth-authz-req+jwt');
|
|
349
|
+
} catch(error) {
|
|
350
|
+
return _sendOID4Error({res, error});
|
|
351
|
+
}
|
|
352
|
+
res.send(result);
|
|
320
353
|
}));
|
|
321
354
|
|
|
322
355
|
// an OID4VP verifier endpoint
|
|
@@ -330,7 +363,35 @@ export async function createRoutes({
|
|
|
330
363
|
getConfigMiddleware,
|
|
331
364
|
getExchange,
|
|
332
365
|
asyncHandler(async (req, res) => {
|
|
333
|
-
|
|
366
|
+
let result;
|
|
367
|
+
try {
|
|
368
|
+
result = await oid4vp.processAuthorizationResponse({req});
|
|
369
|
+
} catch(error) {
|
|
370
|
+
return _sendOID4Error({res, error});
|
|
371
|
+
}
|
|
334
372
|
res.json(result);
|
|
335
373
|
}));
|
|
336
374
|
}
|
|
375
|
+
|
|
376
|
+
function _sendOID4Error({res, error}) {
|
|
377
|
+
const status = error.details?.httpStatusCode ?? 500;
|
|
378
|
+
const oid4Error = {
|
|
379
|
+
error: _camelToSnakeCase(error.name ?? 'OperationError'),
|
|
380
|
+
error_description: error.message
|
|
381
|
+
};
|
|
382
|
+
if(error?.details?.public) {
|
|
383
|
+
oid4Error.details = error.details;
|
|
384
|
+
// expose first level cause only
|
|
385
|
+
if(oid4Error.cause?.details?.public) {
|
|
386
|
+
oid4Error.cause = {
|
|
387
|
+
name: error.cause.name,
|
|
388
|
+
message: error.cause.message
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
res.status(status).json(oid4Error);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function _camelToSnakeCase(s) {
|
|
396
|
+
return s.replace(/[A-Z]/g, (c, i) => (i === 0 ? '' : '_') + c.toLowerCase());
|
|
397
|
+
}
|
package/lib/oid4/oid4vci.js
CHANGED
|
@@ -552,9 +552,10 @@ async function _requestDidProof({res, exchangeRecord}) {
|
|
|
552
552
|
const {exchange, meta: {expires}} = exchangeRecord;
|
|
553
553
|
const ttl = Math.floor((expires.getTime() - Date.now()) / 1000);
|
|
554
554
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
555
|
+
_sendOID4Error({
|
|
556
|
+
res,
|
|
557
|
+
error: 'invalid_proof',
|
|
558
|
+
description:
|
|
558
559
|
'Credential issuer requires proof element in Credential Request',
|
|
559
560
|
// use exchange ID
|
|
560
561
|
c_nonce: exchange.id,
|
|
@@ -588,10 +589,19 @@ async function _requestOID4VP({authorizationRequest, res}) {
|
|
|
588
589
|
challenge to be signed is just the exchange ID itself. An exchange cannot
|
|
589
590
|
be reused and neither can a challenge. */
|
|
590
591
|
|
|
591
|
-
|
|
592
|
+
_sendOID4Error({
|
|
593
|
+
res,
|
|
592
594
|
error: 'presentation_required',
|
|
593
|
-
|
|
595
|
+
description:
|
|
594
596
|
'Credential issuer requires presentation before Credential Request',
|
|
595
597
|
authorization_request: authorizationRequest
|
|
596
598
|
});
|
|
597
599
|
}
|
|
600
|
+
|
|
601
|
+
function _sendOID4Error({res, error, description, status = 400, ...rest}) {
|
|
602
|
+
res.status(status).json({
|
|
603
|
+
error,
|
|
604
|
+
error_description: description,
|
|
605
|
+
...rest
|
|
606
|
+
});
|
|
607
|
+
}
|