@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 +124 -66
- package/lib/oid4/oid4vci.js +15 -5
- package/package.json +1 -1
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
+
}
|
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
|
+
}
|