@bedrock/vc-delivery 5.4.0 → 5.5.1

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
@@ -12,6 +12,7 @@ import {
12
12
  import {asyncHandler} from '@bedrock/express';
13
13
  import bodyParser from 'body-parser';
14
14
  import cors from 'cors';
15
+ import {logger} from '../logger.js';
15
16
  import {UnsecuredJWT} from 'jose';
16
17
  import {createValidateMiddleware as validate} from '@bedrock/validation';
17
18
 
@@ -156,8 +157,13 @@ export async function createRoutes({
156
157
  getConfigMiddleware,
157
158
  getExchange,
158
159
  asyncHandler(async (req, res) => {
159
- const response = await oid4vci.processAccessTokenRequest({req, res});
160
- res.json(response);
160
+ let result;
161
+ try {
162
+ result = await oid4vci.processAccessTokenRequest({req, res});
163
+ } catch(error) {
164
+ return _sendOID4Error({res, error});
165
+ }
166
+ res.json(result);
161
167
  }));
162
168
 
163
169
  // a credential delivery server endpoint
@@ -195,41 +201,46 @@ export async function createRoutes({
195
201
  }
196
202
  }
197
203
  */
198
- const result = await oid4vci.processCredentialRequests({
199
- req, res, isBatchRequest: false
200
- });
201
- if(!result) {
202
- // DID proof request response sent
203
- return;
204
- }
205
-
206
- // send VC(s)
207
- const {
208
- response: {verifiablePresentation: {verifiableCredential}},
209
- format
210
- } = result;
211
- // FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
212
- const credentials = verifiableCredential.map(vc => {
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;
204
+ let result;
205
+ try {
206
+ result = await oid4vci.processCredentialRequests({
207
+ req, res, isBatchRequest: false
208
+ });
209
+ if(!result) {
210
+ // DID proof request response sent
211
+ return;
220
212
  }
221
- return credential;
222
- });
223
213
 
224
- /* Note: The `/credential` route only supports sending VCs of the same
225
- type, but there can be more than one of them. The above `isBatchRequest`
226
- check will ensure that the workflow used here only allows a single
227
- credential request, indicating a single type. */
214
+ // send VC(s)
215
+ const {
216
+ response: {verifiablePresentation: {verifiableCredential}},
217
+ format
218
+ } = result;
219
+ // FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
220
+ const credentials = verifiableCredential.map(vc => {
221
+ // parse any enveloped VC
222
+ let credential;
223
+ if(vc.type === 'EnvelopedVerifiableCredential' &&
224
+ vc.id?.startsWith('data:application/jwt,')) {
225
+ credential = vc.id.slice('data:application/jwt,'.length);
226
+ } else {
227
+ credential = vc;
228
+ }
229
+ return credential;
230
+ });
231
+
232
+ /* Note: The `/credential` route only supports sending VCs of the same
233
+ type, but there can be more than one of them. The above `isBatchRequest`
234
+ check will ensure that the workflow used here only allows a single
235
+ credential request, indicating a single type. */
228
236
 
229
- // send OID4VCI response
230
- const response = credentials.length === 1 ?
231
- {format, credential: credentials[0]} : {format, credentials};
232
- res.json(response);
237
+ // send OID4VCI response
238
+ result = credentials.length === 1 ?
239
+ {format, credential: credentials[0]} : {format, credentials};
240
+ } catch(error) {
241
+ return _sendOID4Error({res, error});
242
+ }
243
+ res.json(result);
233
244
  }));
234
245
 
235
246
  // a credential delivery server endpoint
@@ -240,8 +251,13 @@ export async function createRoutes({
240
251
  getConfigMiddleware,
241
252
  getExchange,
242
253
  asyncHandler(async (req, res) => {
243
- const offer = await oid4vci.getCredentialOffer({req});
244
- res.json(offer);
254
+ let result;
255
+ try {
256
+ result = await oid4vci.getCredentialOffer({req});
257
+ } catch(error) {
258
+ return _sendOID4Error({res, error});
259
+ }
260
+ res.json(result);
245
261
  }));
246
262
 
247
263
  // a batch credential delivery server endpoint
@@ -281,29 +297,37 @@ export async function createRoutes({
281
297
  }]
282
298
  }
283
299
  */
284
- const result = await oid4vci.processCredentialRequests({
285
- req, res, isBatchRequest: true
286
- });
287
- if(!result) {
288
- // DID proof request response sent
289
- return;
290
- }
291
-
292
- // send VCs
293
- const {response: {verifiablePresentation}, format} = result;
294
- // FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
295
- const responses = verifiablePresentation.verifiableCredential.map(vc => {
296
- // parse any enveloped VC
297
- let credential;
298
- if(vc.type === 'EnvelopedVerifiableCredential' &&
299
- vc.id?.startsWith('data:application/jwt,')) {
300
- credential = vc.id.slice('data:application/jwt,'.length);
301
- } else {
302
- credential = vc;
300
+ let result;
301
+ try {
302
+ result = await oid4vci.processCredentialRequests({
303
+ req, res, isBatchRequest: true
304
+ });
305
+ if(!result) {
306
+ // DID proof request response sent
307
+ return;
303
308
  }
304
- return {format, credential};
305
- });
306
- res.json({credential_responses: responses});
309
+
310
+ // send VCs
311
+ const {
312
+ response: {verifiablePresentation: {verifiableCredential}},
313
+ format
314
+ } = result;
315
+ // FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
316
+ result = verifiableCredential.map(vc => {
317
+ // parse any enveloped VC
318
+ let credential;
319
+ if(vc.type === 'EnvelopedVerifiableCredential' &&
320
+ vc.id?.startsWith('data:application/jwt,')) {
321
+ credential = vc.id.slice('data:application/jwt,'.length);
322
+ } else {
323
+ credential = vc;
324
+ }
325
+ return {format, credential};
326
+ });
327
+ } catch(error) {
328
+ return _sendOID4Error({res, error});
329
+ }
330
+ res.json({credential_responses: result});
307
331
  }));
308
332
 
309
333
  // an OID4VP verifier endpoint
@@ -315,13 +339,18 @@ export async function createRoutes({
315
339
  getConfigMiddleware,
316
340
  getExchange,
317
341
  asyncHandler(async (req, res) => {
318
- const {
319
- authorizationRequest
320
- } = await oid4vp.getAuthorizationRequest({req});
321
- // construct and send authz request as unsecured JWT
322
- const jwt = new UnsecuredJWT(authorizationRequest).encode();
323
- res.set('content-type', 'application/oauth-authz-req+jwt');
324
- res.send(jwt);
342
+ let result;
343
+ try {
344
+ const {
345
+ authorizationRequest
346
+ } = await oid4vp.getAuthorizationRequest({req});
347
+ // construct and send authz request as unsecured JWT
348
+ result = new UnsecuredJWT(authorizationRequest).encode();
349
+ res.set('content-type', 'application/oauth-authz-req+jwt');
350
+ } catch(error) {
351
+ return _sendOID4Error({res, error});
352
+ }
353
+ res.send(result);
325
354
  }));
326
355
 
327
356
  // an OID4VP verifier endpoint
@@ -335,7 +364,36 @@ export async function createRoutes({
335
364
  getConfigMiddleware,
336
365
  getExchange,
337
366
  asyncHandler(async (req, res) => {
338
- const result = await oid4vp.processAuthorizationResponse({req});
367
+ let result;
368
+ try {
369
+ result = await oid4vp.processAuthorizationResponse({req});
370
+ } catch(error) {
371
+ return _sendOID4Error({res, error});
372
+ }
339
373
  res.json(result);
340
374
  }));
341
375
  }
376
+
377
+ function _sendOID4Error({res, error}) {
378
+ logger.error(error.message, {error});
379
+ const status = error.details?.httpStatusCode ?? 500;
380
+ const oid4Error = {
381
+ error: _camelToSnakeCase(error.name ?? 'OperationError'),
382
+ error_description: error.message
383
+ };
384
+ if(error?.details?.public) {
385
+ oid4Error.details = error.details;
386
+ // expose first level cause only
387
+ if(oid4Error.cause?.details?.public) {
388
+ oid4Error.cause = {
389
+ name: error.cause.name,
390
+ message: error.cause.message
391
+ };
392
+ }
393
+ }
394
+ res.status(status).json(oid4Error);
395
+ }
396
+
397
+ function _camelToSnakeCase(s) {
398
+ return s.replace(/[A-Z]/g, (c, i) => (i === 0 ? '' : '_') + c.toLowerCase());
399
+ }
@@ -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.4.0",
3
+ "version": "5.5.1",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",