@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 CHANGED
@@ -156,8 +156,13 @@ export async function createRoutes({
156
156
  getConfigMiddleware,
157
157
  getExchange,
158
158
  asyncHandler(async (req, res) => {
159
- const response = await oid4vci.processAccessTokenRequest({req, res});
160
- res.json(response);
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
- const result = await oid4vci.processCredentialRequests({
199
- req, res, isBatchRequest: false
200
- });
201
- if(!result) {
202
- // DID proof request response sent
203
- return;
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
- /* Note: The `/credential` route only supports sending a single VC;
207
- assume here that this workflow is configured for a single VC and an
208
- error code would have been sent to the client to use the batch
209
- endpoint if there was more than one VC to deliver. */
210
- const {response, format} = result;
211
- const {verifiablePresentation: {verifiableCredential: [vc]}} = response;
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
- // parse any enveloped VC
214
- let credential;
215
- if(vc.type === 'EnvelopedVerifiableCredential' &&
216
- vc.id?.startsWith('data:application/jwt,')) {
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
- // send OID4VCI response
223
- res.json({
224
- // FIXME: this doesn't seem to be in the spec anymore (draft 14+)...
225
- format,
226
- credential
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
- const offer = await oid4vci.getCredentialOffer({req});
239
- res.json(offer);
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
- const result = await oid4vci.processCredentialRequests({
280
- req, res, isBatchRequest: true
281
- });
282
- if(!result) {
283
- // DID proof request response sent
284
- return;
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
- return {format, credential};
300
- });
301
- res.json({credential_responses: responses});
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
- const {
314
- authorizationRequest
315
- } = await oid4vp.getAuthorizationRequest({req});
316
- // construct and send authz request as unsecured JWT
317
- const jwt = new UnsecuredJWT(authorizationRequest).encode();
318
- res.set('content-type', 'application/oauth-authz-req+jwt');
319
- res.send(jwt);
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
- const result = await oid4vp.processAuthorizationResponse({req});
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
+ }
@@ -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
- res.status(400).json({
556
- error: 'invalid_or_missing_proof',
557
- error_description:
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
- res.status(400).json({
592
+ _sendOID4Error({
593
+ res,
592
594
  error: 'presentation_required',
593
- error_description:
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "5.3.5",
3
+ "version": "5.5.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",