@digitalbazaar/oid4-client 4.2.0 → 4.4.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/OID4Client.js +88 -27
- package/lib/authorizationRequest.js +344 -0
- package/lib/authorizationResponse.js +312 -0
- package/lib/convert.js +440 -0
- package/lib/index.js +3 -2
- package/lib/oid4vp.js +20 -836
- package/lib/util.js +50 -1
- package/package.json +8 -9
package/lib/OID4Client.js
CHANGED
|
@@ -18,8 +18,47 @@ export class OID4Client {
|
|
|
18
18
|
this.offer = offer;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
async getNonce({agent, headers = HEADERS} = {}) {
|
|
22
|
+
let response;
|
|
23
|
+
try {
|
|
24
|
+
// get nonce endpoint
|
|
25
|
+
const {nonce_endpoint: url} = this.issuerConfig;
|
|
26
|
+
if(url === undefined) {
|
|
27
|
+
const error = new Error('Credential issuer has no "nonce_endpoint".');
|
|
28
|
+
error.name = 'DataError';
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
if(!url?.startsWith('https://')) {
|
|
32
|
+
const error = new Error(
|
|
33
|
+
`Nonce endpoint "${url}" does not start with "https://".`);
|
|
34
|
+
error.name = 'DataError';
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// get nonce
|
|
39
|
+
response = await httpClient.post(url, {agent, headers});
|
|
40
|
+
if(!response.data) {
|
|
41
|
+
const error = new Error('Nonce response format is not JSON.');
|
|
42
|
+
error.name = 'DataError';
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if(response.data.c_nonce === undefined) {
|
|
46
|
+
const error = new Error('Nonce not provided in response.');
|
|
47
|
+
error.name = 'DataError';
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
} catch(cause) {
|
|
51
|
+
const error = new Error('Could not get nonce.', {cause});
|
|
52
|
+
error.name = 'DataError';
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {c_nonce: nonce} = response.data;
|
|
57
|
+
return {nonce, response};
|
|
58
|
+
}
|
|
59
|
+
|
|
21
60
|
async requestCredential({
|
|
22
|
-
credentialDefinition, did, didProofSigner, agent, format = 'ldp_vc'
|
|
61
|
+
credentialDefinition, did, didProofSigner, nonce, agent, format = 'ldp_vc'
|
|
23
62
|
} = {}) {
|
|
24
63
|
const {issuerConfig, offer} = this;
|
|
25
64
|
let requests;
|
|
@@ -41,13 +80,21 @@ export class OID4Client {
|
|
|
41
80
|
credential_definition: credentialDefinition
|
|
42
81
|
}];
|
|
43
82
|
}
|
|
44
|
-
return this.requestCredentials({
|
|
83
|
+
return this.requestCredentials({
|
|
84
|
+
requests, did, didProofSigner, nonce, agent
|
|
85
|
+
});
|
|
45
86
|
}
|
|
46
87
|
|
|
47
88
|
async requestCredentials({
|
|
48
|
-
requests, did, didProofSigner, agent, format = 'ldp_vc',
|
|
89
|
+
requests, did, didProofSigner, agent, nonce, format = 'ldp_vc',
|
|
49
90
|
alwaysUseBatchEndpoint = false
|
|
50
91
|
} = {}) {
|
|
92
|
+
// if `nonce` is given, then `did` and `didProofSigner` must also be
|
|
93
|
+
if(nonce !== undefined && !(did && didProofSigner)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
'If "nonce" is given then "did" and "didProofSigner" are required.');
|
|
96
|
+
}
|
|
97
|
+
|
|
51
98
|
const {issuerConfig, offer} = this;
|
|
52
99
|
if(requests === undefined && offer) {
|
|
53
100
|
requests = _createCredentialRequestsFromOffer({
|
|
@@ -61,7 +108,8 @@ export class OID4Client {
|
|
|
61
108
|
requests = requests.map(r => ({format, ...r}));
|
|
62
109
|
|
|
63
110
|
try {
|
|
64
|
-
/* First send credential request(s) to DS without DID proof JWT
|
|
111
|
+
/* First send credential request(s) to DS without DID proof JWT (unless
|
|
112
|
+
`nonce` is given) e.g.:
|
|
65
113
|
|
|
66
114
|
POST /credential HTTP/1.1
|
|
67
115
|
Host: server.example.com
|
|
@@ -71,7 +119,7 @@ export class OID4Client {
|
|
|
71
119
|
{
|
|
72
120
|
"format": "ldp_vc",
|
|
73
121
|
"credential_definition": {...},
|
|
74
|
-
// only present on retry after server requests it
|
|
122
|
+
// only present on retry after server requests it or if nonce is given
|
|
75
123
|
"proof": {
|
|
76
124
|
"proof_type": "jwt",
|
|
77
125
|
"jwt": "eyJraW..."
|
|
@@ -109,6 +157,11 @@ export class OID4Client {
|
|
|
109
157
|
json = {...requests[0]};
|
|
110
158
|
}
|
|
111
159
|
|
|
160
|
+
if(nonce !== undefined) {
|
|
161
|
+
// add DID proof JWT to json
|
|
162
|
+
await _addDIDProofJWT({issuerConfig, json, nonce, did, didProofSigner});
|
|
163
|
+
}
|
|
164
|
+
|
|
112
165
|
let result;
|
|
113
166
|
const headers = {
|
|
114
167
|
...HEADERS,
|
|
@@ -149,32 +202,16 @@ export class OID4Client {
|
|
|
149
202
|
}
|
|
150
203
|
|
|
151
204
|
// validate that `result` has
|
|
152
|
-
|
|
205
|
+
let {data: {c_nonce: nonce}} = cause;
|
|
153
206
|
if(!(nonce && typeof nonce === 'string')) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
throw error;
|
|
207
|
+
// try to get a nonce
|
|
208
|
+
({nonce} = await this.getNonce({agent}));
|
|
157
209
|
}
|
|
158
210
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
signer: didProofSigner,
|
|
163
|
-
nonce,
|
|
164
|
-
// the entity identified by the DID is issuing this JWT
|
|
165
|
-
iss: did,
|
|
166
|
-
// audience MUST be the target issuer per the OID4VCI spec
|
|
167
|
-
aud
|
|
211
|
+
// add DID proof JWT to json
|
|
212
|
+
await _addDIDProofJWT({
|
|
213
|
+
issuerConfig, json, nonce, did, didProofSigner
|
|
168
214
|
});
|
|
169
|
-
|
|
170
|
-
// add proof to body to be posted and loop to retry
|
|
171
|
-
const proof = {proof_type: 'jwt', jwt};
|
|
172
|
-
if(json.credential_requests) {
|
|
173
|
-
json.credential_requests = json.credential_requests.map(
|
|
174
|
-
cr => ({...cr, proof}));
|
|
175
|
-
} else {
|
|
176
|
-
json.proof = proof;
|
|
177
|
-
}
|
|
178
215
|
}
|
|
179
216
|
}
|
|
180
217
|
|
|
@@ -344,6 +381,30 @@ export class OID4Client {
|
|
|
344
381
|
}
|
|
345
382
|
}
|
|
346
383
|
|
|
384
|
+
async function _addDIDProofJWT({
|
|
385
|
+
issuerConfig, json, nonce, did, didProofSigner
|
|
386
|
+
}) {
|
|
387
|
+
// generate a DID proof JWT
|
|
388
|
+
const {issuer: aud} = issuerConfig;
|
|
389
|
+
const jwt = await generateDIDProofJWT({
|
|
390
|
+
signer: didProofSigner,
|
|
391
|
+
nonce,
|
|
392
|
+
// the entity identified by the DID is issuing this JWT
|
|
393
|
+
iss: did,
|
|
394
|
+
// audience MUST be the target issuer per the OID4VCI spec
|
|
395
|
+
aud
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// add proof to body to be posted and loop to retry
|
|
399
|
+
const proof = {proof_type: 'jwt', jwt};
|
|
400
|
+
if(json.credential_requests) {
|
|
401
|
+
json.credential_requests = json.credential_requests.map(
|
|
402
|
+
cr => ({...cr, proof}));
|
|
403
|
+
} else {
|
|
404
|
+
json.proof = proof;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
347
408
|
function _assertRequest(request) {
|
|
348
409
|
if(!(request && typeof request === 'object')) {
|
|
349
410
|
throw new TypeError('"request" must be an object.');
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
assert, assertOptional, createNamedError, fetchJSON, selectJwk
|
|
6
|
+
} from './util.js';
|
|
7
|
+
import {decodeJwt, jwtVerify} from 'jose';
|
|
8
|
+
|
|
9
|
+
// get an authorization request from a verifier
|
|
10
|
+
export async function get({url, getVerificationKey, agent} = {}) {
|
|
11
|
+
try {
|
|
12
|
+
assert(url, 'url', 'string');
|
|
13
|
+
|
|
14
|
+
let authorizationRequest;
|
|
15
|
+
let requestUrl;
|
|
16
|
+
let expectedClientId;
|
|
17
|
+
if(url.startsWith('https://')) {
|
|
18
|
+
// the request must be retrieved via HTTP
|
|
19
|
+
requestUrl = url;
|
|
20
|
+
} else {
|
|
21
|
+
// parse the request from the given URL
|
|
22
|
+
({authorizationRequest} = _parseOID4VPUrl({url}));
|
|
23
|
+
expectedClientId = authorizationRequest.client_id;
|
|
24
|
+
if(authorizationRequest.request_uri) {
|
|
25
|
+
requestUrl = authorizationRequest.request_uri;
|
|
26
|
+
}
|
|
27
|
+
// if whole request is passed by reference, then it MUST be a signed JWT
|
|
28
|
+
if(authorizationRequest.request) {
|
|
29
|
+
authorizationRequest = await _parseJwt({
|
|
30
|
+
jwt: authorizationRequest.request,
|
|
31
|
+
getVerificationKey,
|
|
32
|
+
signatureRequired: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// fetch request if necessary...
|
|
38
|
+
let fetched = false;
|
|
39
|
+
let response;
|
|
40
|
+
let jwt;
|
|
41
|
+
if(requestUrl) {
|
|
42
|
+
fetched = true;
|
|
43
|
+
({
|
|
44
|
+
payload: authorizationRequest, response, jwt
|
|
45
|
+
} = await _fetch({requestUrl, getVerificationKey, agent}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ensure authorization request is valid
|
|
49
|
+
validate({authorizationRequest, expectedClientId});
|
|
50
|
+
|
|
51
|
+
// resolve and validate any additional parameters in the request
|
|
52
|
+
authorizationRequest = await resolveParams({authorizationRequest, agent});
|
|
53
|
+
|
|
54
|
+
return {authorizationRequest, fetched, requestUrl, response, jwt};
|
|
55
|
+
} catch(cause) {
|
|
56
|
+
const message = cause.data?.error_description ?? cause.message;
|
|
57
|
+
throw createNamedError({
|
|
58
|
+
message: `Could not get authorization request: ${message}`,
|
|
59
|
+
name: 'OperationError',
|
|
60
|
+
cause
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function requestsFormat({authorizationRequest, format} = {}) {
|
|
66
|
+
/* e.g. presentation definition requesting an mdoc:
|
|
67
|
+
{
|
|
68
|
+
id: 'mdl-test-age-over-21',
|
|
69
|
+
input_descriptors: [{
|
|
70
|
+
id: 'org.iso.18013.5.1.mDL',
|
|
71
|
+
format: {
|
|
72
|
+
mso_mdoc: {
|
|
73
|
+
alg: ['ES256']
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}]
|
|
77
|
+
}
|
|
78
|
+
*/
|
|
79
|
+
return authorizationRequest.presentation_definition?.input_descriptors?.some(
|
|
80
|
+
e => e?.format?.[format]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// FIXME: in a major release, remove support for params requiring resolution
|
|
84
|
+
export async function resolveParams({authorizationRequest, agent}) {
|
|
85
|
+
const {
|
|
86
|
+
client_metadata_uri,
|
|
87
|
+
presentation_definition_uri,
|
|
88
|
+
...resolved
|
|
89
|
+
} = {...authorizationRequest};
|
|
90
|
+
|
|
91
|
+
// get client meta data from URL if specified
|
|
92
|
+
if(client_metadata_uri) {
|
|
93
|
+
const response = await fetchJSON({url: client_metadata_uri, agent});
|
|
94
|
+
if(!response.data) {
|
|
95
|
+
throw createNamedError({
|
|
96
|
+
message: 'Client meta data format is not JSON.',
|
|
97
|
+
name: 'DataError'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
resolved.client_metadata = response.data;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// get presentation definition from URL if not embedded
|
|
104
|
+
if(presentation_definition_uri) {
|
|
105
|
+
const response = await fetchJSON(
|
|
106
|
+
{url: presentation_definition_uri, agent});
|
|
107
|
+
if(!response.data) {
|
|
108
|
+
throw createNamedError({
|
|
109
|
+
message: 'Presentation definition format is not JSON.',
|
|
110
|
+
name: 'DataError'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
resolved.presentation_definition = response.data;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
assert(resolved.presentation_definition, 'presentation_definition', 'object');
|
|
117
|
+
assert(
|
|
118
|
+
resolved.presentation_definition?.id,
|
|
119
|
+
'presentation_definition.id', 'string');
|
|
120
|
+
|
|
121
|
+
// FIXME: further validate `authorizationRequest.presentation_definition`
|
|
122
|
+
// FIXME: further validate `authorizationRequest.client_metadata`
|
|
123
|
+
|
|
124
|
+
// `direct_post.jwt` response mode requires encryption; ensure the client
|
|
125
|
+
// meta data has the necessary parameters
|
|
126
|
+
if(resolved.response_mode === 'direct_post.jwt') {
|
|
127
|
+
const {
|
|
128
|
+
authorization_encrypted_response_alg = 'ECDH-ES',
|
|
129
|
+
authorization_encrypted_response_enc = 'A256GCM',
|
|
130
|
+
jwks
|
|
131
|
+
} = resolved.client_metadata;
|
|
132
|
+
if(authorization_encrypted_response_alg !== 'ECDH-ES') {
|
|
133
|
+
throw createNamedError({
|
|
134
|
+
message: `"${authorization_encrypted_response_alg}" is not ` +
|
|
135
|
+
'supported; only "ECDH-ES" is supported.',
|
|
136
|
+
name: 'NotSupportedError'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if(authorization_encrypted_response_enc !== 'A256GCM') {
|
|
140
|
+
throw createNamedError({
|
|
141
|
+
message: `"${authorization_encrypted_response_enc}" is not ` +
|
|
142
|
+
'supported; only "A256GCM" is supported.',
|
|
143
|
+
name: 'NotSupportedError'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if(!selectJwk({
|
|
147
|
+
keys: jwks?.keys, alg: 'ECDH-ES', kty: 'EC', crv: 'P-256', use: 'enc'
|
|
148
|
+
})) {
|
|
149
|
+
throw createNamedError({
|
|
150
|
+
message: 'No matching key found for "ECDH-ES" in client meta data ' +
|
|
151
|
+
'JWK key set.',
|
|
152
|
+
name: 'NotFoundError'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return resolved;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function usesClientIdScheme({authorizationRequest, scheme} = {}) {
|
|
161
|
+
return authorizationRequest?.client_id_scheme === scheme ||
|
|
162
|
+
authorizationRequest?.client_id?.startsWith(`${scheme}:`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function validate({authorizationRequest, expectedClientId}) {
|
|
166
|
+
// validate payload (expected authorization request)
|
|
167
|
+
const {
|
|
168
|
+
client_id,
|
|
169
|
+
client_id_scheme,
|
|
170
|
+
client_metadata,
|
|
171
|
+
client_metadata_uri,
|
|
172
|
+
nonce,
|
|
173
|
+
presentation_definition,
|
|
174
|
+
presentation_definition_uri,
|
|
175
|
+
response_mode,
|
|
176
|
+
scope
|
|
177
|
+
} = authorizationRequest;
|
|
178
|
+
assert(client_id, 'client_id', 'string');
|
|
179
|
+
// ensure `client_id` matches expected client ID
|
|
180
|
+
if(expectedClientId !== undefined && client_id !== expectedClientId) {
|
|
181
|
+
throw createNamedError({
|
|
182
|
+
message: '"client_id" in fetched request does not match authorization ' +
|
|
183
|
+
'request URL parameter.',
|
|
184
|
+
name: 'DataError'
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
assert(nonce, 'nonce', 'string');
|
|
188
|
+
assertOptional(client_id_scheme, 'client_id_scheme', 'string');
|
|
189
|
+
assertOptional(client_metadata, 'client_metadata', 'object');
|
|
190
|
+
// FIXME: remove `client_metadata_uri` in a future revision, it is not
|
|
191
|
+
// supported in the latest OID4VP, bad practice, and rarely used
|
|
192
|
+
assertOptional(client_metadata_uri, 'client_metadata_uri', 'string');
|
|
193
|
+
assertOptional(
|
|
194
|
+
presentation_definition, 'presentation_definition', 'object');
|
|
195
|
+
// FIXME: remove `presentation_definition_uri` in a future revision, it is
|
|
196
|
+
// not supported in the latest OID4VP, bad practice, and rarely used
|
|
197
|
+
assertOptional(
|
|
198
|
+
presentation_definition_uri, 'presentation_definition_uri', 'string');
|
|
199
|
+
assertOptional(response_mode, 'response_mode', 'string');
|
|
200
|
+
assertOptional(scope, 'scope', 'string');
|
|
201
|
+
if(client_metadata && client_metadata_uri) {
|
|
202
|
+
throw createNamedError({
|
|
203
|
+
message: 'Only one of "client_metadata" and ' +
|
|
204
|
+
'"client_metadata_uri" must be present.',
|
|
205
|
+
name: 'DataError'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if(presentation_definition && presentation_definition_uri) {
|
|
209
|
+
throw createNamedError({
|
|
210
|
+
message: 'Only one of "presentation_definition" and ' +
|
|
211
|
+
'"presentation_definition_uri" must be present.',
|
|
212
|
+
name: 'DataError'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// Note: This implementation requires `response_mode` to be `direct_post`
|
|
216
|
+
// or `direct_post.jwt`; no other modes are supported.
|
|
217
|
+
if(!(response_mode === 'direct_post' ||
|
|
218
|
+
response_mode === 'direct_post.jwt')) {
|
|
219
|
+
throw createNamedError({
|
|
220
|
+
message: 'Only "direct_post" and "direct_post.jwt" ' +
|
|
221
|
+
'response modes are supported.',
|
|
222
|
+
name: 'NotSupportedError'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function _fetch({requestUrl, getVerificationKey, agent}) {
|
|
228
|
+
// FIXME: every `fetchJSON` call needs to use a block list or other
|
|
229
|
+
// protections to prevent a confused deputy attack where the `requestUrl`
|
|
230
|
+
// accesses a location it should not, e.g., a URL `localhost` is used when
|
|
231
|
+
// it shouldn't be
|
|
232
|
+
const response = await fetchJSON({url: requestUrl, agent});
|
|
233
|
+
|
|
234
|
+
// parse payload from response data...
|
|
235
|
+
const contentType = response.headers.get('content-type');
|
|
236
|
+
const jwt = await response.text();
|
|
237
|
+
|
|
238
|
+
// verify response is a JWT-secured authorization request
|
|
239
|
+
if(!(contentType.includes('application/oauth-authz-req+jwt') &&
|
|
240
|
+
typeof jwt === 'string')) {
|
|
241
|
+
throw createNamedError({
|
|
242
|
+
message: 'Authorization request content-type must be ' +
|
|
243
|
+
'"application/oauth-authz-req+jwt".',
|
|
244
|
+
name: 'DataError'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// return parsed payload and original response
|
|
249
|
+
const payload = await _parseJwt({jwt, getVerificationKey});
|
|
250
|
+
return {payload, response, jwt};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function _get(sp, name) {
|
|
254
|
+
const value = sp.get(name);
|
|
255
|
+
return value === null ? undefined : value;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function _parseJwt({jwt, getVerificationKey, signatureRequired}) {
|
|
259
|
+
const payload = decodeJwt(jwt);
|
|
260
|
+
|
|
261
|
+
// check if a signature on the JWT is required:
|
|
262
|
+
// - `client_metadata.require_signed_request_object` is `true`, OR
|
|
263
|
+
// - `client_id_scheme` requires the JWT to be signed
|
|
264
|
+
signatureRequired = signatureRequired ||
|
|
265
|
+
payload.client_metadata?.require_signed_request_object === true ||
|
|
266
|
+
usesClientIdScheme({authorizationRequest: payload, scheme: 'x509_san_dns'});
|
|
267
|
+
if(!signatureRequired) {
|
|
268
|
+
// no signature required, just use the decoded payload
|
|
269
|
+
return payload;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// create callback function to handle key lookup; `getVerificationKey` may
|
|
273
|
+
// return a promise
|
|
274
|
+
const getKey = protectedHeader => {
|
|
275
|
+
// FIXME: add parser for `x5c`?
|
|
276
|
+
if(getVerificationKey) {
|
|
277
|
+
return getVerificationKey({protectedHeader});
|
|
278
|
+
}
|
|
279
|
+
_throwKeyNotFound(protectedHeader);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// verify the JWT
|
|
283
|
+
const verifyResult = await jwtVerify(jwt, getKey, {alg: 'ES256'});
|
|
284
|
+
return verifyResult.payload;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function _parseOID4VPUrl({url}) {
|
|
288
|
+
const {searchParams} = new URL(url);
|
|
289
|
+
const request = _get(searchParams, 'request');
|
|
290
|
+
const request_uri = _get(searchParams, 'request_uri');
|
|
291
|
+
const response_type = _get(searchParams, 'response_type');
|
|
292
|
+
const response_mode = _get(searchParams, 'response_mode');
|
|
293
|
+
const presentation_definition = _get(
|
|
294
|
+
searchParams, 'presentation_definition');
|
|
295
|
+
const presentation_definition_uri = _get(
|
|
296
|
+
searchParams, 'presentation_definition_uri');
|
|
297
|
+
const client_id = _get(searchParams, 'client_id');
|
|
298
|
+
const client_id_scheme = _get(searchParams, 'client_id_scheme');
|
|
299
|
+
const client_metadata = _get(searchParams, 'client_metadata');
|
|
300
|
+
const nonce = _get(searchParams, 'nonce');
|
|
301
|
+
const response_uri = _get(searchParams, 'response_uri');
|
|
302
|
+
const state = _get(searchParams, 'state');
|
|
303
|
+
if(request && request_uri) {
|
|
304
|
+
const error = createNamedError({
|
|
305
|
+
message: 'Only one of "request" and "request_uri" may be present.',
|
|
306
|
+
name: 'DataError'
|
|
307
|
+
});
|
|
308
|
+
error.url = url;
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
if(!(request || request_uri)) {
|
|
312
|
+
if(response_type !== 'vp_token') {
|
|
313
|
+
throw new Error(`Unsupported "response_type", "${response_type}".`);
|
|
314
|
+
}
|
|
315
|
+
if(!(response_mode === 'direct_post' ||
|
|
316
|
+
response_mode === 'direct_post.jwt')) {
|
|
317
|
+
throw new Error(`Unsupported "response_type", "${response_type}".`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const authorizationRequest = {
|
|
321
|
+
request,
|
|
322
|
+
request_uri,
|
|
323
|
+
response_type,
|
|
324
|
+
response_mode,
|
|
325
|
+
presentation_definition: presentation_definition &&
|
|
326
|
+
JSON.parse(presentation_definition),
|
|
327
|
+
presentation_definition_uri,
|
|
328
|
+
client_id,
|
|
329
|
+
client_id_scheme,
|
|
330
|
+
client_metadata: client_metadata && JSON.parse(client_metadata),
|
|
331
|
+
response_uri,
|
|
332
|
+
nonce,
|
|
333
|
+
state
|
|
334
|
+
};
|
|
335
|
+
return {authorizationRequest};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function _throwKeyNotFound(protectedHeader) {
|
|
339
|
+
const error = new Error(
|
|
340
|
+
'Could not verify signed authorization request; ' +
|
|
341
|
+
`public key "${protectedHeader.kid}" not found.`);
|
|
342
|
+
error.name = 'NotFoundError';
|
|
343
|
+
throw error;
|
|
344
|
+
}
|