@bedrock/vc-delivery 5.4.0 → 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 -66
- 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,41 +200,46 @@ export async function createRoutes({
|
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
202
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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;
|
|
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;
|
|
220
211
|
}
|
|
221
|
-
return credential;
|
|
222
|
-
});
|
|
223
212
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
});
|
|
230
|
+
|
|
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. */
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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);
|
|
233
243
|
}));
|
|
234
244
|
|
|
235
245
|
// a credential delivery server endpoint
|
|
@@ -240,8 +250,13 @@ export async function createRoutes({
|
|
|
240
250
|
getConfigMiddleware,
|
|
241
251
|
getExchange,
|
|
242
252
|
asyncHandler(async (req, res) => {
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
let result;
|
|
254
|
+
try {
|
|
255
|
+
result = await oid4vci.getCredentialOffer({req});
|
|
256
|
+
} catch(error) {
|
|
257
|
+
return _sendOID4Error({res, error});
|
|
258
|
+
}
|
|
259
|
+
res.json(result);
|
|
245
260
|
}));
|
|
246
261
|
|
|
247
262
|
// a batch credential delivery server endpoint
|
|
@@ -281,29 +296,37 @@ export async function createRoutes({
|
|
|
281
296
|
}]
|
|
282
297
|
}
|
|
283
298
|
*/
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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;
|
|
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;
|
|
303
307
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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});
|
|
307
330
|
}));
|
|
308
331
|
|
|
309
332
|
// an OID4VP verifier endpoint
|
|
@@ -315,13 +338,18 @@ export async function createRoutes({
|
|
|
315
338
|
getConfigMiddleware,
|
|
316
339
|
getExchange,
|
|
317
340
|
asyncHandler(async (req, res) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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);
|
|
325
353
|
}));
|
|
326
354
|
|
|
327
355
|
// an OID4VP verifier endpoint
|
|
@@ -335,7 +363,35 @@ export async function createRoutes({
|
|
|
335
363
|
getConfigMiddleware,
|
|
336
364
|
getExchange,
|
|
337
365
|
asyncHandler(async (req, res) => {
|
|
338
|
-
|
|
366
|
+
let result;
|
|
367
|
+
try {
|
|
368
|
+
result = await oid4vp.processAuthorizationResponse({req});
|
|
369
|
+
} catch(error) {
|
|
370
|
+
return _sendOID4Error({res, error});
|
|
371
|
+
}
|
|
339
372
|
res.json(result);
|
|
340
373
|
}));
|
|
341
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
|
+
}
|