@bedrock/vc-delivery 7.13.2 → 7.14.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.
@@ -0,0 +1,197 @@
1
+ /*!
2
+ * Copyright (c) 2022-2026 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as bedrock from '@bedrock/core';
5
+ import {deepEqual, getWorkflowIssuerInstances} from '../helpers.js';
6
+ import {randomUUID as uuid} from 'node:crypto';
7
+
8
+ const {util: {BedrockError}} = bedrock;
9
+
10
+ export function createSupportedCredentialConfigurations({
11
+ exchange, issuerInstances
12
+ } = {}) {
13
+ // get legacy `expectedCredentialRequests`
14
+ const {
15
+ openId: {expectedCredentialRequests}
16
+ } = exchange;
17
+
18
+ // build legacy supported credential configurations...
19
+ const supported = new Map();
20
+
21
+ // get supported formats from issuer instances
22
+ const supportedFormats = new Set();
23
+ issuerInstances.forEach(instance => instance.supportedFormats.forEach(
24
+ supportedFormats.add, supportedFormats));
25
+
26
+ // for every expected credential request and supported format, generate a
27
+ // supported credential configuration
28
+ for(const credentialRequest of expectedCredentialRequests) {
29
+ const configurations = _createCredentialConfigurations({
30
+ credentialRequest, supportedFormats
31
+ });
32
+ for(const {id, configuration} of configurations) {
33
+ supported.set(id, configuration);
34
+ }
35
+ }
36
+
37
+ return Object.fromEntries(supported.entries());
38
+ }
39
+
40
+ export function createSupportedCredentialRequests({
41
+ workflow, exchange, issueRequestsParams
42
+ } = {}) {
43
+ const issuerInstances = getWorkflowIssuerInstances({workflow});
44
+ const supported = createSupportedCredentialConfigurations({
45
+ exchange, issuerInstances
46
+ });
47
+
48
+ // for each `issueRequest` params, create a duplicate for each supported
49
+ // credential configuration
50
+ const ids = Object.keys(supported);
51
+ const supportedCredentialRequests = [];
52
+ for(let i = 0; i < issueRequestsParams.length; ++i) {
53
+ for(const credentialConfigurationId of ids) {
54
+ supportedCredentialRequests.push({
55
+ credentialConfigurationId,
56
+ credentialIdentifier: uuid(),
57
+ issueRequestsParamsIndex: i,
58
+ processed: false,
59
+ });
60
+ }
61
+ }
62
+
63
+ return supportedCredentialRequests;
64
+ }
65
+
66
+ export function normalizeCredentialRequestsToVersion1({
67
+ credentialRequests, supportedCredentialConfigurations
68
+ }) {
69
+ // normalize credential requests to use `type` instead of `types`; this is to
70
+ // allow for OID4VCI draft implementers that followed the non-normative
71
+ // examples
72
+ credentialRequests = _normalizeCredentialDefinitionTypes({
73
+ credentialRequests
74
+ });
75
+
76
+ // match draft 13 requests against credential configurations
77
+ return _matchCredentialRequests({
78
+ credentialRequests, supportedCredentialConfigurations
79
+ });
80
+ }
81
+
82
+ function _createCredentialConfigurationId({
83
+ format, credential_definition
84
+ }) {
85
+ let types = (credential_definition.type ?? credential_definition.types);
86
+ if(types.length > 1) {
87
+ types = types.filter(t => t !== 'VerifiableCredential');
88
+ }
89
+ return types.join('_') + '_' + format;
90
+ }
91
+
92
+ function _matchCredentialRequest(expected, cr) {
93
+ const {credential_definition: {'@context': c1, type: t1}} = expected;
94
+ const {credential_definition: {'@context': c2, type: t2}} = cr;
95
+ // contexts must match exactly but types can have different order
96
+ return (c1.length === c2.length && t1.length === t2.length &&
97
+ deepEqual(c1, c2) && t1.every(t => t2.some(x => t === x)));
98
+ }
99
+
100
+ function _matchCredentialRequests({
101
+ credentialRequests, supportedCredentialConfigurations
102
+ }) {
103
+ // ensure that every credential request is for the same format
104
+ /* credential requests look like:
105
+ {
106
+ format: 'ldp_vc',
107
+ credential_definition: { '@context': [Array], type: [Array] }
108
+ }
109
+ */
110
+ let sharedFormat;
111
+ if(!credentialRequests.every(({format}) => {
112
+ if(sharedFormat === undefined) {
113
+ sharedFormat = format;
114
+ }
115
+ return sharedFormat === format;
116
+ })) {
117
+ throw new BedrockError(
118
+ 'Credential requests in a batch must all use the same format.', {
119
+ name: 'DataError',
120
+ details: {httpStatusCode: 400, public: true}
121
+ });
122
+ }
123
+
124
+ // ensure every credential request matches a supported configuration
125
+ const entries = Object.entries(supportedCredentialConfigurations);
126
+ return credentialRequests.map(cr => {
127
+ for(const [credential_configuration_id, configuration] of entries) {
128
+ if(_matchCredentialRequest(configuration, cr)) {
129
+ const newRequest = {
130
+ type: 'openid_credential',
131
+ credential_configuration_id
132
+ };
133
+ // only proof type supported for draft 13 is `jwt` per JSON schema that
134
+ // has already run
135
+ if(cr.proof) {
136
+ newRequest.proofs = {
137
+ jwt: [cr.proof.jwt]
138
+ };
139
+ }
140
+ return newRequest;
141
+ }
142
+ }
143
+ throw new BedrockError(
144
+ 'Unexpected credential request.', {
145
+ name: 'DataError',
146
+ details: {httpStatusCode: 400, public: true}
147
+ });
148
+ });
149
+ }
150
+
151
+ function _createCredentialConfigurations({
152
+ credentialRequest, supportedFormats
153
+ }) {
154
+ const configurations = [];
155
+
156
+ let {format: formats = supportedFormats} = credentialRequest;
157
+ if(!Array.isArray(formats)) {
158
+ formats = [formats];
159
+ }
160
+
161
+ for(const format of formats) {
162
+ const {credential_definition} = credentialRequest;
163
+ const id = _createCredentialConfigurationId({
164
+ format, credential_definition
165
+ });
166
+ const configuration = {format, credential_definition};
167
+ // FIXME: if `jwtDidProofRequest` exists in (any) step in the exchange,
168
+ // then must include:
169
+ /*
170
+ "proof_types_supported": {
171
+ "jwt": {
172
+ "proof_signing_alg_values_supported": [
173
+ "ES256"
174
+ ]
175
+ }
176
+ }
177
+ */
178
+ configurations.push({id, configuration});
179
+ }
180
+
181
+ return configurations;
182
+ }
183
+
184
+ function _normalizeCredentialDefinitionTypes({credentialRequests}) {
185
+ // normalize credential requests to use `type` instead of `types`
186
+ return credentialRequests.map(cr => {
187
+ if(!cr?.credential_definition?.types) {
188
+ return cr;
189
+ }
190
+ cr = {...cr};
191
+ if(!cr.credential_definition.type) {
192
+ cr.credential_definition.type = cr.credential_definition.types;
193
+ }
194
+ delete cr.credential_definition.types;
195
+ return cr;
196
+ });
197
+ }
@@ -191,7 +191,12 @@ export async function processAuthorizationResponse({req, clientProfileId}) {
191
191
  }) {
192
192
  const {authorizationRequest} = result;
193
193
  verifyPresentationOptions.challenge = authorizationRequest.nonce;
194
- verifyPresentationOptions.domain = authorizationRequest.response_uri;
194
+ const usesRedirectUri = oid4vp.authzRequest.usesClientIdScheme({
195
+ authorizationRequest, scheme: 'redirect_uri'
196
+ });
197
+ verifyPresentationOptions.domain = usesRedirectUri ?
198
+ authorizationRequest.client_id : authorizationRequest.response_uri;
199
+ expectedDomain = verifyPresentationOptions.domain;
195
200
 
196
201
  const {envelope} = parseResponseResult;
197
202
  if(envelope?.mediaType === 'application/mdl-vp-token') {
package/lib/vcapi.js CHANGED
@@ -107,6 +107,7 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
107
107
  const receivedPresentation = req?.body?.verifiablePresentation;
108
108
 
109
109
  // use exchange processor to generate a response
110
+ let stepComplete = true;
110
111
  const exchangeProcessor = new ExchangeProcessor({
111
112
  workflow, exchangeRecord,
112
113
  prepareStep({workflow, step}) {
@@ -118,8 +119,32 @@ export async function processExchange({req, res, workflow, exchangeRecord}) {
118
119
  });
119
120
  }
120
121
  },
121
- inputRequired({step}) {
122
- return step.verifiablePresentationRequest && !receivedPresentation;
122
+ inputRequired({exchange, step, receivedPresentation}) {
123
+ // default to completing step
124
+ stepComplete = true;
125
+
126
+ // if no VP is requested then no input is required
127
+ if(!step.verifiablePresentationRequest) {
128
+ return false;
129
+ }
130
+
131
+ // a VP is requested; if one was provided, allow it to be stored in the
132
+ // step results without issuing anything, but mark the step as incomplete
133
+ // to allow for reprocessing against the stored results
134
+ if(receivedPresentation) {
135
+ step.issueRequests = [];
136
+ step.verifiablePresentation = undefined;
137
+ stepComplete = false;
138
+ return false;
139
+ }
140
+
141
+ // if no VP was previously stored in the step results, then input is
142
+ // required, otherwise it is not
143
+ const stepResults = exchange.variables.results[exchange.step];
144
+ return !stepResults?.verifiablePresentation;
145
+ },
146
+ isStepComplete() {
147
+ return stepComplete;
123
148
  }
124
149
  });
125
150
  const response = await exchangeProcessor.process({receivedPresentation});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "7.13.2",
3
+ "version": "7.14.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",
@@ -40,7 +40,7 @@
40
40
  "@digitalbazaar/ed25519-signature-2020": "^5.4.0",
41
41
  "@digitalbazaar/ezcap": "^4.1.0",
42
42
  "@digitalbazaar/http-client": "^4.2.0",
43
- "@digitalbazaar/oid4-client": "^5.9.0",
43
+ "@digitalbazaar/oid4-client": "^5.10.0",
44
44
  "@digitalbazaar/vc": "^7.2.0",
45
45
  "@digitalbazaar/webkms-client": "^14.2.0",
46
46
  "assert-plus": "^1.0.0",