@openid4vc/oauth2 0.2.0-alpha-20250109205223
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/README.md +9 -0
- package/dist/index.d.mts +2276 -0
- package/dist/index.d.ts +2276 -0
- package/dist/index.js +2703 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2641 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2641 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { getGlobalConfig, setGlobalConfig } from "@openid4vc/utils";
|
|
3
|
+
|
|
4
|
+
// src/common/v-oauth2-error.ts
|
|
5
|
+
import * as v from "valibot";
|
|
6
|
+
var Oauth2ErrorCodes = /* @__PURE__ */ ((Oauth2ErrorCodes2) => {
|
|
7
|
+
Oauth2ErrorCodes2["ServerError"] = "server_error";
|
|
8
|
+
Oauth2ErrorCodes2["InvalidTarget"] = "invalid_target";
|
|
9
|
+
Oauth2ErrorCodes2["InvalidRequest"] = "invalid_request";
|
|
10
|
+
Oauth2ErrorCodes2["InvalidToken"] = "invalid_token";
|
|
11
|
+
Oauth2ErrorCodes2["InsufficientScope"] = "insufficient_scope";
|
|
12
|
+
Oauth2ErrorCodes2["InvalidGrant"] = "invalid_grant";
|
|
13
|
+
Oauth2ErrorCodes2["InvalidClient"] = "invalid_client";
|
|
14
|
+
Oauth2ErrorCodes2["UnauthorizedClient"] = "unauthorized_client";
|
|
15
|
+
Oauth2ErrorCodes2["UnsupportedGrantType"] = "unsupported_grant_type";
|
|
16
|
+
Oauth2ErrorCodes2["InvalidScope"] = "invalid_scope";
|
|
17
|
+
Oauth2ErrorCodes2["InvalidDpopProof"] = "invalid_dpop_proof";
|
|
18
|
+
Oauth2ErrorCodes2["UseDpopNonce"] = "use_dpop_nonce";
|
|
19
|
+
Oauth2ErrorCodes2["RedirectToWeb"] = "redirect_to_web";
|
|
20
|
+
Oauth2ErrorCodes2["InvalidSession"] = "invalid_session";
|
|
21
|
+
Oauth2ErrorCodes2["InsufficientAuthorization"] = "insufficient_authorization";
|
|
22
|
+
Oauth2ErrorCodes2["InvalidCredentialRequest"] = "invalid_credential_request";
|
|
23
|
+
Oauth2ErrorCodes2["CredentialRequestDenied"] = "credential_request_denied";
|
|
24
|
+
Oauth2ErrorCodes2["UnsupportedCredentialType"] = "unsupported_credential_type";
|
|
25
|
+
Oauth2ErrorCodes2["UnsupportedCredentialFormat"] = "unsupported_credential_format";
|
|
26
|
+
Oauth2ErrorCodes2["InvalidProof"] = "invalid_proof";
|
|
27
|
+
Oauth2ErrorCodes2["InvalidNonce"] = "invalid_nonce";
|
|
28
|
+
Oauth2ErrorCodes2["InvalidEncryptionParameters"] = "invalid_encryption_parameters";
|
|
29
|
+
return Oauth2ErrorCodes2;
|
|
30
|
+
})(Oauth2ErrorCodes || {});
|
|
31
|
+
var vOauth2ErrorResponse = v.looseObject({
|
|
32
|
+
error: v.union([v.enum(Oauth2ErrorCodes), v.string()]),
|
|
33
|
+
error_description: v.optional(v.string()),
|
|
34
|
+
error_uri: v.optional(v.string())
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// src/common/jwk/jwk-thumbprint.ts
|
|
38
|
+
import * as v2 from "valibot";
|
|
39
|
+
import { decodeUtf8String, encodeToBase64Url, parseWithErrorHandling } from "@openid4vc/utils";
|
|
40
|
+
var vJwkThumbprintComponents = v2.variant("kty", [
|
|
41
|
+
v2.pipe(
|
|
42
|
+
v2.looseObject({
|
|
43
|
+
kty: v2.literal("EC"),
|
|
44
|
+
crv: v2.string(),
|
|
45
|
+
x: v2.string(),
|
|
46
|
+
y: v2.string()
|
|
47
|
+
}),
|
|
48
|
+
v2.transform(({ crv, kty, x, y }) => ({ crv, kty, x, y }))
|
|
49
|
+
),
|
|
50
|
+
v2.pipe(
|
|
51
|
+
v2.looseObject({
|
|
52
|
+
kty: v2.literal("OKP"),
|
|
53
|
+
crv: v2.string(),
|
|
54
|
+
x: v2.string()
|
|
55
|
+
}),
|
|
56
|
+
v2.transform(({ crv, kty, x }) => ({ crv, kty, x }))
|
|
57
|
+
),
|
|
58
|
+
v2.pipe(
|
|
59
|
+
v2.looseObject({
|
|
60
|
+
kty: v2.literal("RSA"),
|
|
61
|
+
e: v2.string(),
|
|
62
|
+
n: v2.string()
|
|
63
|
+
}),
|
|
64
|
+
v2.transform(({ e, kty, n }) => ({ e, kty, n }))
|
|
65
|
+
),
|
|
66
|
+
v2.pipe(
|
|
67
|
+
v2.looseObject({
|
|
68
|
+
kty: v2.literal("oct"),
|
|
69
|
+
k: v2.string()
|
|
70
|
+
}),
|
|
71
|
+
v2.transform(({ k, kty }) => ({ k, kty }))
|
|
72
|
+
)
|
|
73
|
+
]);
|
|
74
|
+
async function calculateJwkThumbprint(options) {
|
|
75
|
+
const jwkThumbprintComponents = parseWithErrorHandling(
|
|
76
|
+
vJwkThumbprintComponents,
|
|
77
|
+
options.jwk,
|
|
78
|
+
`Provided jwk does not match a supported jwk structure. Either the 'kty' is not supported, or required values are missing.`
|
|
79
|
+
);
|
|
80
|
+
const thumbprint = encodeToBase64Url(
|
|
81
|
+
await options.hashCallback(decodeUtf8String(JSON.stringify(jwkThumbprintComponents)), options.hashAlgorithm)
|
|
82
|
+
);
|
|
83
|
+
return thumbprint;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/callbacks.ts
|
|
87
|
+
var HashAlgorithm = /* @__PURE__ */ ((HashAlgorithm2) => {
|
|
88
|
+
HashAlgorithm2["Sha256"] = "SHA-256";
|
|
89
|
+
return HashAlgorithm2;
|
|
90
|
+
})(HashAlgorithm || {});
|
|
91
|
+
|
|
92
|
+
// src/error/Oauth2Error.ts
|
|
93
|
+
var Oauth2Error = class extends Error {
|
|
94
|
+
constructor(message, options) {
|
|
95
|
+
const errorMessage = message ?? "Unknown error occured.";
|
|
96
|
+
const causeMessage = options?.cause instanceof Error ? ` ${options.cause.message}` : options?.cause ? ` ${options?.cause}` : "";
|
|
97
|
+
super(`${errorMessage}${causeMessage}`);
|
|
98
|
+
this.cause = options?.cause;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/common/jwk/jwks.ts
|
|
103
|
+
function extractJwkFromJwksForJwt(options) {
|
|
104
|
+
const jwksForUse = options.jwks.keys.filter(({ use }) => !use || use === options.use);
|
|
105
|
+
const jwkForKid = options.kid ? jwksForUse.find(({ kid }) => kid === options.kid) : void 0;
|
|
106
|
+
if (jwkForKid) {
|
|
107
|
+
return jwkForKid;
|
|
108
|
+
}
|
|
109
|
+
if (jwksForUse.length === 1) {
|
|
110
|
+
return jwksForUse[0];
|
|
111
|
+
}
|
|
112
|
+
throw new Oauth2Error(
|
|
113
|
+
`Unable to extract jwk from jwks for use '${options.use}'${options.kid ? `with kid '${options.kid}'.` : ". No kid provided and more than jwk."}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
async function isJwkInSet({
|
|
117
|
+
jwk,
|
|
118
|
+
jwks,
|
|
119
|
+
callbacks
|
|
120
|
+
}) {
|
|
121
|
+
const jwkThumbprint = await calculateJwkThumbprint({
|
|
122
|
+
hashAlgorithm: "SHA-256" /* Sha256 */,
|
|
123
|
+
hashCallback: callbacks.hash,
|
|
124
|
+
jwk
|
|
125
|
+
});
|
|
126
|
+
for (const jwkFromSet of jwks) {
|
|
127
|
+
const jwkFromSetThumbprint = await calculateJwkThumbprint({
|
|
128
|
+
hashAlgorithm: "SHA-256" /* Sha256 */,
|
|
129
|
+
hashCallback: callbacks.hash,
|
|
130
|
+
jwk: jwkFromSet
|
|
131
|
+
});
|
|
132
|
+
if (jwkFromSetThumbprint === jwkThumbprint) return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/common/jwk/v-jwk.ts
|
|
138
|
+
import * as v3 from "valibot";
|
|
139
|
+
var vJwk = v3.looseObject({
|
|
140
|
+
kty: v3.string(),
|
|
141
|
+
crv: v3.optional(v3.string()),
|
|
142
|
+
x: v3.optional(v3.string()),
|
|
143
|
+
y: v3.optional(v3.string()),
|
|
144
|
+
e: v3.optional(v3.string()),
|
|
145
|
+
n: v3.optional(v3.string()),
|
|
146
|
+
alg: v3.optional(v3.string()),
|
|
147
|
+
d: v3.optional(v3.string()),
|
|
148
|
+
dp: v3.optional(v3.string()),
|
|
149
|
+
dq: v3.optional(v3.string()),
|
|
150
|
+
ext: v3.optional(v3.boolean()),
|
|
151
|
+
k: v3.optional(v3.string()),
|
|
152
|
+
key_ops: v3.optional(v3.string()),
|
|
153
|
+
kid: v3.optional(v3.string()),
|
|
154
|
+
oth: v3.optional(
|
|
155
|
+
v3.array(
|
|
156
|
+
v3.looseObject({
|
|
157
|
+
d: v3.optional(v3.string()),
|
|
158
|
+
r: v3.optional(v3.string()),
|
|
159
|
+
t: v3.optional(v3.string())
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
),
|
|
163
|
+
p: v3.optional(v3.string()),
|
|
164
|
+
q: v3.optional(v3.string()),
|
|
165
|
+
qi: v3.optional(v3.string()),
|
|
166
|
+
use: v3.optional(v3.string()),
|
|
167
|
+
x5c: v3.optional(v3.string()),
|
|
168
|
+
x5t: v3.optional(v3.string()),
|
|
169
|
+
"x5t#S256": v3.optional(v3.string()),
|
|
170
|
+
x5u: v3.optional(v3.string())
|
|
171
|
+
});
|
|
172
|
+
var vJwkSet = v3.looseObject({
|
|
173
|
+
keys: v3.array(vJwk)
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// src/common/jwt/verify-jwt.ts
|
|
177
|
+
import { dateToSeconds } from "@openid4vc/utils";
|
|
178
|
+
|
|
179
|
+
// src/error/Oauth2JwtVerificationError.ts
|
|
180
|
+
var Oauth2JwtVerificationError = class extends Oauth2Error {
|
|
181
|
+
constructor(message, options) {
|
|
182
|
+
const errorMessage = message ?? "Error verifiying jwt.";
|
|
183
|
+
super(errorMessage, options);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/common/jwt/verify-jwt.ts
|
|
188
|
+
async function verifyJwt(options) {
|
|
189
|
+
const errorMessage = options.errorMessage ?? "Error during verification of jwt.";
|
|
190
|
+
let signerJwk;
|
|
191
|
+
try {
|
|
192
|
+
const result = await options.verifyJwtCallback(options.signer, {
|
|
193
|
+
header: options.header,
|
|
194
|
+
payload: options.payload,
|
|
195
|
+
compact: options.compact
|
|
196
|
+
});
|
|
197
|
+
if (!result.verified) throw new Oauth2JwtVerificationError(errorMessage);
|
|
198
|
+
signerJwk = result.signerJwk;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error instanceof Oauth2JwtVerificationError) throw error;
|
|
201
|
+
throw new Oauth2JwtVerificationError(errorMessage, { cause: error });
|
|
202
|
+
}
|
|
203
|
+
const nowInSeconds = dateToSeconds(options.now ?? /* @__PURE__ */ new Date());
|
|
204
|
+
const skewInSeconds = options.allowedSkewInSeconds ?? 0;
|
|
205
|
+
const timeBasedValidation = options.skipTimeBasedValidation !== void 0 ? !options.skipTimeBasedValidation : true;
|
|
206
|
+
if (timeBasedValidation && options.payload.nbf && nowInSeconds < options.payload.nbf - skewInSeconds) {
|
|
207
|
+
throw new Oauth2JwtVerificationError(`${errorMessage} jwt 'nbf' is in the future`);
|
|
208
|
+
}
|
|
209
|
+
if (timeBasedValidation && options.payload.exp && nowInSeconds > options.payload.exp + skewInSeconds) {
|
|
210
|
+
throw new Oauth2JwtVerificationError(`${errorMessage} jwt 'exp' is in the past`);
|
|
211
|
+
}
|
|
212
|
+
if (options.expectedAudience && options.expectedAudience !== options.payload.aud) {
|
|
213
|
+
throw new Oauth2JwtVerificationError(`${errorMessage} jwt 'aud' does not match expected value.`);
|
|
214
|
+
}
|
|
215
|
+
if (options.expectedIssuer && options.expectedIssuer !== options.payload.iss) {
|
|
216
|
+
throw new Oauth2JwtVerificationError(`${errorMessage} jwt 'iss' does not match expected value.`);
|
|
217
|
+
}
|
|
218
|
+
if (options.expectedNonce && options.expectedNonce !== options.payload.nonce) {
|
|
219
|
+
throw new Oauth2JwtVerificationError(`${errorMessage} jwt 'nonce' does not match expected value.`);
|
|
220
|
+
}
|
|
221
|
+
if (options.expectedSubject && options.expectedSubject !== options.payload.sub) {
|
|
222
|
+
throw new Oauth2JwtVerificationError(`${errorMessage} jwt 'sub' does not match expected value.`);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
signer: {
|
|
226
|
+
...options.signer,
|
|
227
|
+
publicJwk: signerJwk
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/common/jwt/decode-jwt.ts
|
|
233
|
+
import {
|
|
234
|
+
decodeBase64,
|
|
235
|
+
encodeToUtf8String,
|
|
236
|
+
parseWithErrorHandling as parseWithErrorHandling2,
|
|
237
|
+
stringToJsonWithErrorHandling
|
|
238
|
+
} from "@openid4vc/utils";
|
|
239
|
+
|
|
240
|
+
// src/error/Oauth2JwtParseError.ts
|
|
241
|
+
var Oauth2JwtParseError = class extends Oauth2Error {
|
|
242
|
+
constructor(message) {
|
|
243
|
+
const errorMessage = message ?? "Error parsing jwt";
|
|
244
|
+
super(errorMessage);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/common/jwt/v-jwt.ts
|
|
249
|
+
import { vInteger } from "@openid4vc/utils";
|
|
250
|
+
import * as v5 from "valibot";
|
|
251
|
+
|
|
252
|
+
// src/common/v-common.ts
|
|
253
|
+
import * as v4 from "valibot";
|
|
254
|
+
var vAlgValueNotNone = v4.pipe(
|
|
255
|
+
v4.string(),
|
|
256
|
+
v4.check((alg) => alg !== "none", `alg value may not be 'none'`)
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// src/common/jwt/v-jwt.ts
|
|
260
|
+
var vCompactJwt = v5.pipe(
|
|
261
|
+
v5.string(),
|
|
262
|
+
v5.regex(/^([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_\-\+\/=]*)$/, "Not a valid compact jwt")
|
|
263
|
+
);
|
|
264
|
+
var vJwtConfirmationPayload = v5.looseObject({
|
|
265
|
+
jwk: v5.optional(vJwk),
|
|
266
|
+
// RFC9449. jwk thumbprint of the dpop public key to which the access token is bound
|
|
267
|
+
jkt: v5.optional(v5.string())
|
|
268
|
+
});
|
|
269
|
+
var vJwtPayload = v5.looseObject({
|
|
270
|
+
iss: v5.optional(v5.string()),
|
|
271
|
+
aud: v5.optional(v5.string()),
|
|
272
|
+
iat: v5.optional(vInteger),
|
|
273
|
+
exp: v5.optional(vInteger),
|
|
274
|
+
nbf: v5.optional(vInteger),
|
|
275
|
+
nonce: v5.optional(v5.string()),
|
|
276
|
+
jti: v5.optional(v5.string()),
|
|
277
|
+
cnf: v5.optional(vJwtConfirmationPayload),
|
|
278
|
+
// Reserved for status parameters
|
|
279
|
+
status: v5.optional(v5.looseObject({}))
|
|
280
|
+
});
|
|
281
|
+
var vJwtHeader = v5.looseObject({
|
|
282
|
+
alg: vAlgValueNotNone,
|
|
283
|
+
typ: v5.optional(v5.string()),
|
|
284
|
+
kid: v5.optional(v5.string()),
|
|
285
|
+
jwk: v5.optional(vJwk),
|
|
286
|
+
x5c: v5.optional(v5.array(v5.string())),
|
|
287
|
+
trust_chain: v5.optional(v5.array(v5.string()))
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// src/common/jwt/decode-jwt.ts
|
|
291
|
+
function decodeJwt(options) {
|
|
292
|
+
const jwtParts = options.jwt.split(".");
|
|
293
|
+
if (jwtParts.length !== 3) {
|
|
294
|
+
throw new Oauth2JwtParseError("Jwt is not a valid jwt, unable to decode");
|
|
295
|
+
}
|
|
296
|
+
let headerJson;
|
|
297
|
+
let payloadJson;
|
|
298
|
+
try {
|
|
299
|
+
headerJson = stringToJsonWithErrorHandling(
|
|
300
|
+
encodeToUtf8String(decodeBase64(jwtParts[0])),
|
|
301
|
+
"Unable to parse jwt header to JSON"
|
|
302
|
+
);
|
|
303
|
+
payloadJson = stringToJsonWithErrorHandling(
|
|
304
|
+
encodeToUtf8String(decodeBase64(jwtParts[1])),
|
|
305
|
+
"Unable to parse jwt payload to JSON"
|
|
306
|
+
);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new Oauth2JwtParseError("Error parsing JWT");
|
|
309
|
+
}
|
|
310
|
+
const header = parseWithErrorHandling2(options.headerSchema ?? vJwtHeader, headerJson);
|
|
311
|
+
const payload = parseWithErrorHandling2(options.payloadSchema ?? vJwtPayload, payloadJson);
|
|
312
|
+
return {
|
|
313
|
+
header,
|
|
314
|
+
payload,
|
|
315
|
+
signature: jwtParts[2]
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function jwtHeaderFromJwtSigner(signer) {
|
|
319
|
+
if (signer.method === "did") {
|
|
320
|
+
return {
|
|
321
|
+
alg: signer.alg,
|
|
322
|
+
kid: signer.didUrl
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (signer.method === "trustChain") {
|
|
326
|
+
return {
|
|
327
|
+
alg: signer.alg,
|
|
328
|
+
kid: signer.kid,
|
|
329
|
+
trust_chain: signer.trustChain
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (signer.method === "jwk") {
|
|
333
|
+
return {
|
|
334
|
+
alg: signer.alg,
|
|
335
|
+
jwk: signer.publicJwk,
|
|
336
|
+
kid: signer.publicJwk.kid
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (signer.method === "x5c") {
|
|
340
|
+
return {
|
|
341
|
+
alg: signer.alg,
|
|
342
|
+
x5c: signer.x5c
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
alg: signer.alg
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function jwtSignerFromJwt({ header, payload }) {
|
|
350
|
+
if (header.x5c) {
|
|
351
|
+
return {
|
|
352
|
+
alg: header.alg,
|
|
353
|
+
method: "x5c",
|
|
354
|
+
x5c: header.x5c
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (header.trust_chain) {
|
|
358
|
+
if (!header.kid) {
|
|
359
|
+
throw new Error(`When 'trust_chain' is used in jwt header, the 'kid' parameter is required.`);
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
method: "trustChain",
|
|
363
|
+
alg: header.alg,
|
|
364
|
+
trustChain: header.trust_chain,
|
|
365
|
+
kid: header.kid
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (header.kid) {
|
|
369
|
+
if (header.kid.startsWith("did:")) {
|
|
370
|
+
if (payload.iss && header.kid.startsWith(payload.iss)) {
|
|
371
|
+
}
|
|
372
|
+
if (!header.kid.includes("#")) {
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
method: "did",
|
|
376
|
+
didUrl: header.kid,
|
|
377
|
+
alg: header.alg
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (header.kid.startsWith("#") && payload.iss?.startsWith("did:")) {
|
|
381
|
+
return {
|
|
382
|
+
method: "did",
|
|
383
|
+
didUrl: `${payload.iss}${header.kid}`,
|
|
384
|
+
alg: header.alg
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (header.jwk) {
|
|
389
|
+
return {
|
|
390
|
+
alg: header.alg,
|
|
391
|
+
method: "jwk",
|
|
392
|
+
publicJwk: header.jwk
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
method: "custom",
|
|
397
|
+
alg: header.alg
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/error/Oauth2ResourceUnauthorizedError.ts
|
|
402
|
+
import { encodeWwwAuthenticateHeader, parseWwwAuthenticateHeader } from "@openid4vc/utils";
|
|
403
|
+
var Oauth2ResourceUnauthorizedError = class _Oauth2ResourceUnauthorizedError extends Oauth2Error {
|
|
404
|
+
constructor(internalMessage, wwwAuthenticateHeaders) {
|
|
405
|
+
super(`${internalMessage}
|
|
406
|
+
${JSON.stringify(wwwAuthenticateHeaders, null, 2)}`);
|
|
407
|
+
this.wwwAuthenticateHeaders = Array.isArray(wwwAuthenticateHeaders) ? wwwAuthenticateHeaders : [wwwAuthenticateHeaders];
|
|
408
|
+
}
|
|
409
|
+
static fromHeaderValue(value) {
|
|
410
|
+
const headers = parseWwwAuthenticateHeader(value);
|
|
411
|
+
return new _Oauth2ResourceUnauthorizedError(
|
|
412
|
+
void 0,
|
|
413
|
+
headers.map(
|
|
414
|
+
({ scheme, payload: { error, error_description, scope, ...additionalPayload } }) => ({
|
|
415
|
+
scheme,
|
|
416
|
+
error: Array.isArray(error) ? error.join(",") : error ?? void 0,
|
|
417
|
+
error_description: Array.isArray(error_description) ? error_description.join(",") : error_description ?? void 0,
|
|
418
|
+
scope: Array.isArray(scope) ? scope.join(",") : scope ?? void 0,
|
|
419
|
+
...additionalPayload
|
|
420
|
+
})
|
|
421
|
+
)
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
toHeaderValue() {
|
|
425
|
+
return encodeWwwAuthenticateHeader(
|
|
426
|
+
this.wwwAuthenticateHeaders.map((header) => ({
|
|
427
|
+
scheme: header.scheme,
|
|
428
|
+
payload: {
|
|
429
|
+
error: header.error ?? null,
|
|
430
|
+
error_description: header.error_description ?? null,
|
|
431
|
+
scope: header.scope ?? null,
|
|
432
|
+
...header.additionalPayload
|
|
433
|
+
}
|
|
434
|
+
}))
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/index.ts
|
|
440
|
+
import { InvalidFetchResponseError as InvalidFetchResponseError7 } from "@openid4vc/utils";
|
|
441
|
+
|
|
442
|
+
// src/error/Oauth2ClientErrorResponseError.ts
|
|
443
|
+
var Oauth2ClientErrorResponseError = class extends Oauth2Error {
|
|
444
|
+
constructor(message, errorResponse, response) {
|
|
445
|
+
super(`${message}
|
|
446
|
+
${JSON.stringify(errorResponse, null, 2)}`);
|
|
447
|
+
this.errorResponse = errorResponse;
|
|
448
|
+
this.response = response.clone();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// src/error/Oauth2ClientAuthorizationChallengeError.ts
|
|
453
|
+
var Oauth2ClientAuthorizationChallengeError = class extends Oauth2ClientErrorResponseError {
|
|
454
|
+
constructor(message, errorResponse, response) {
|
|
455
|
+
super(message, errorResponse, response);
|
|
456
|
+
this.errorResponse = errorResponse;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/error/Oauth2ServerErrorResponseError.ts
|
|
461
|
+
var Oauth2ServerErrorResponseError = class extends Oauth2Error {
|
|
462
|
+
constructor(errorResponse, options) {
|
|
463
|
+
super(
|
|
464
|
+
`${options?.internalMessage ?? errorResponse.error_description}
|
|
465
|
+
${JSON.stringify(errorResponse, null, 2)}`,
|
|
466
|
+
options
|
|
467
|
+
);
|
|
468
|
+
this.errorResponse = errorResponse;
|
|
469
|
+
this.status = options?.status ?? 400;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/metadata/authorization-server/v-authorization-server-metadata.ts
|
|
474
|
+
import { vHttpsUrl } from "@openid4vc/utils";
|
|
475
|
+
import * as v6 from "valibot";
|
|
476
|
+
var vAuthorizationServerMetadata = v6.pipe(
|
|
477
|
+
v6.looseObject({
|
|
478
|
+
issuer: vHttpsUrl,
|
|
479
|
+
token_endpoint: vHttpsUrl,
|
|
480
|
+
token_endpoint_auth_methods_supported: v6.optional(v6.array(v6.string())),
|
|
481
|
+
authorization_endpoint: v6.optional(vHttpsUrl),
|
|
482
|
+
jwks_uri: v6.optional(vHttpsUrl),
|
|
483
|
+
// RFC7636
|
|
484
|
+
code_challenge_methods_supported: v6.optional(v6.array(v6.string())),
|
|
485
|
+
// RFC9449
|
|
486
|
+
dpop_signing_alg_values_supported: v6.optional(v6.array(v6.string())),
|
|
487
|
+
// RFC9126
|
|
488
|
+
require_pushed_authorization_requests: v6.optional(v6.boolean()),
|
|
489
|
+
pushed_authorization_request_endpoint: v6.optional(vHttpsUrl),
|
|
490
|
+
// RFC9068
|
|
491
|
+
introspection_endpoint: v6.optional(vHttpsUrl),
|
|
492
|
+
introspection_endpoint_auth_methods_supported: v6.optional(
|
|
493
|
+
v6.array(v6.union([v6.literal("client_secret_jwt"), v6.literal("private_key_jwt"), v6.string()]))
|
|
494
|
+
),
|
|
495
|
+
introspection_endpoint_auth_signing_alg_values_supported: v6.optional(v6.array(vAlgValueNotNone)),
|
|
496
|
+
// FiPA (no RFC yet)
|
|
497
|
+
authorization_challenge_endpoint: v6.optional(vHttpsUrl),
|
|
498
|
+
// From OID4VCI specification
|
|
499
|
+
"pre-authorized_grant_anonymous_access_supported": v6.optional(v6.boolean())
|
|
500
|
+
}),
|
|
501
|
+
v6.check(
|
|
502
|
+
({
|
|
503
|
+
introspection_endpoint_auth_methods_supported: methodsSupported,
|
|
504
|
+
introspection_endpoint_auth_signing_alg_values_supported: algValuesSupported
|
|
505
|
+
}) => {
|
|
506
|
+
if (!methodsSupported) return true;
|
|
507
|
+
if (!methodsSupported.includes("private_key_jwt") && !methodsSupported.includes("client_secret_jwt")) return true;
|
|
508
|
+
return algValuesSupported !== void 0 && algValuesSupported.length > 0;
|
|
509
|
+
},
|
|
510
|
+
`Metadata value 'introspection_endpoint_auth_signing_alg_values_supported' must be defined if metadata 'introspection_endpoint_auth_methods_supported' value contains values 'private_key_jwt' or 'client_secret_jwt'`
|
|
511
|
+
)
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// src/metadata/authorization-server/authorization-server-metadata.ts
|
|
515
|
+
import { joinUriParts } from "@openid4vc/utils";
|
|
516
|
+
|
|
517
|
+
// src/metadata/fetch-well-known-metadata.ts
|
|
518
|
+
import { ContentType, createValibotFetcher } from "@openid4vc/utils";
|
|
519
|
+
import { InvalidFetchResponseError } from "@openid4vc/utils";
|
|
520
|
+
|
|
521
|
+
// ../utils/src/parse.ts
|
|
522
|
+
import * as v7 from "valibot";
|
|
523
|
+
|
|
524
|
+
// ../utils/src/object.ts
|
|
525
|
+
function isObject(item) {
|
|
526
|
+
return item != null && typeof item === "object" && !Array.isArray(item);
|
|
527
|
+
}
|
|
528
|
+
function mergeDeep(target, ...sources) {
|
|
529
|
+
if (!sources.length) return target;
|
|
530
|
+
const source = sources.shift();
|
|
531
|
+
if (isObject(target) && isObject(source)) {
|
|
532
|
+
for (const key in source) {
|
|
533
|
+
if (isObject(source[key])) {
|
|
534
|
+
if (!target[key]) Object.assign(target, { [key]: {} });
|
|
535
|
+
mergeDeep(target[key], source[key]);
|
|
536
|
+
} else {
|
|
537
|
+
Object.assign(target, { [key]: source[key] });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return mergeDeep(target, ...sources);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ../utils/src/parse.ts
|
|
545
|
+
function valibotRecursiveFlattenIssues(issues) {
|
|
546
|
+
let flattened = v7.flatten(issues);
|
|
547
|
+
for (const issue of issues) {
|
|
548
|
+
if (issue.issues) {
|
|
549
|
+
flattened = mergeDeep(flattened, valibotRecursiveFlattenIssues(issue.issues));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return flattened;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ../utils/src/error/ValidationError.ts
|
|
556
|
+
var ValidationError = class extends Error {
|
|
557
|
+
constructor(message, valibotIssues = []) {
|
|
558
|
+
const errorDetails = valibotIssues.length > 0 ? JSON.stringify(valibotRecursiveFlattenIssues(valibotIssues), null, 2) : "No details provided";
|
|
559
|
+
super(`${message}
|
|
560
|
+
${errorDetails}`);
|
|
561
|
+
this.valibotIssues = valibotIssues;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// src/metadata/fetch-well-known-metadata.ts
|
|
566
|
+
async function fetchWellKnownMetadata(wellKnownMetadataUrl, schema, fetch) {
|
|
567
|
+
const fetcher = createValibotFetcher(fetch);
|
|
568
|
+
const { result, response } = await fetcher(schema, ContentType.Json, wellKnownMetadataUrl);
|
|
569
|
+
if (response.status === 404) {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
if (!response.ok) {
|
|
573
|
+
throw new InvalidFetchResponseError(
|
|
574
|
+
`Fetching well known metadata from '${wellKnownMetadataUrl}' resulted in an unsuccessfull response with status '${response.status}'.`,
|
|
575
|
+
await response.clone().text(),
|
|
576
|
+
response
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
if (!result || !result.success) {
|
|
580
|
+
throw new ValidationError(`Validation of metadata from '${wellKnownMetadataUrl}' failed`, result?.issues);
|
|
581
|
+
}
|
|
582
|
+
return result.output;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/metadata/authorization-server/authorization-server-metadata.ts
|
|
586
|
+
var wellKnownAuthorizationServerSuffix = ".well-known/oauth-authorization-server";
|
|
587
|
+
var wellKnownOpenIdConfigurationServerSuffix = ".well-known/openid-configuration";
|
|
588
|
+
async function fetchAuthorizationServerMetadata(issuer, fetch) {
|
|
589
|
+
const openIdConfigurationWellKnownMetadataUrl = joinUriParts(issuer, [wellKnownOpenIdConfigurationServerSuffix]);
|
|
590
|
+
const openIdConfigurationResult = await fetchWellKnownMetadata(
|
|
591
|
+
openIdConfigurationWellKnownMetadataUrl,
|
|
592
|
+
vAuthorizationServerMetadata,
|
|
593
|
+
fetch
|
|
594
|
+
);
|
|
595
|
+
if (openIdConfigurationResult) {
|
|
596
|
+
if (openIdConfigurationResult.issuer !== issuer) {
|
|
597
|
+
throw new Oauth2Error(
|
|
598
|
+
`The 'issuer' parameter '${openIdConfigurationResult.issuer}' in the well known openid configuration at '${openIdConfigurationWellKnownMetadataUrl}' does not match the provided issuer '${issuer}'.`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return openIdConfigurationResult;
|
|
602
|
+
}
|
|
603
|
+
const authorizationServerWellKnownMetadataUrl = joinUriParts(issuer, [wellKnownAuthorizationServerSuffix]);
|
|
604
|
+
const authorizationServerResult = await fetchWellKnownMetadata(
|
|
605
|
+
authorizationServerWellKnownMetadataUrl,
|
|
606
|
+
vAuthorizationServerMetadata,
|
|
607
|
+
fetch
|
|
608
|
+
);
|
|
609
|
+
if (authorizationServerResult) {
|
|
610
|
+
if (authorizationServerResult.issuer !== issuer) {
|
|
611
|
+
throw new Oauth2Error(
|
|
612
|
+
`The 'issuer' parameter '${authorizationServerResult.issuer}' in the well known authorization server metadata at '${authorizationServerWellKnownMetadataUrl}' does not match the provided issuer '${issuer}'.`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
return authorizationServerResult;
|
|
616
|
+
}
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
function getAuthorizationServerMetadataFromList(authorizationServersMetadata, issuer) {
|
|
620
|
+
const authorizationServerMetadata = authorizationServersMetadata.find(
|
|
621
|
+
(authorizationServerMetadata2) => authorizationServerMetadata2.issuer === issuer
|
|
622
|
+
);
|
|
623
|
+
if (!authorizationServerMetadata) {
|
|
624
|
+
throw new Oauth2Error(
|
|
625
|
+
`Authorization server '${issuer}' not found in list of authorization servers. Availalbe authorization servers are ${authorizationServersMetadata.map((as) => `'${as.issuer}'`).join(", ")}`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
return authorizationServerMetadata;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/metadata/fetch-jwks-uri.ts
|
|
632
|
+
import { ContentType as ContentType2, createValibotFetcher as createValibotFetcher2 } from "@openid4vc/utils";
|
|
633
|
+
import { InvalidFetchResponseError as InvalidFetchResponseError2 } from "@openid4vc/utils";
|
|
634
|
+
async function fetchJwks(authorizationServer, fetch) {
|
|
635
|
+
const fetcher = createValibotFetcher2(fetch);
|
|
636
|
+
const jwksUrl = authorizationServer.jwks_uri;
|
|
637
|
+
if (!jwksUrl) {
|
|
638
|
+
throw new Oauth2Error(
|
|
639
|
+
`Authorization server '${authorizationServer.issuer}' does not have a 'jwks_uri' parameter to fetch JWKs.`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const { result, response } = await fetcher(vJwkSet, ContentType2.JwkSet, jwksUrl);
|
|
643
|
+
if (!response.ok) {
|
|
644
|
+
throw new InvalidFetchResponseError2(
|
|
645
|
+
`Fetching JWKs from jwks_uri '${jwksUrl}' resulted in an unsuccessfull response with status code '${response.status}'.`,
|
|
646
|
+
await response.clone().text(),
|
|
647
|
+
response
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
if (!result || !result.success) {
|
|
651
|
+
throw new ValidationError(`Validation of JWKs from jwks_uri '${jwksUrl}' failed`, result?.issues);
|
|
652
|
+
}
|
|
653
|
+
return result.output;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/access-token/v-access-token-jwt.ts
|
|
657
|
+
import { vInteger as vInteger2 } from "@openid4vc/utils";
|
|
658
|
+
import * as v8 from "valibot";
|
|
659
|
+
var vAccessTokenProfileJwtHeader = v8.looseObject({
|
|
660
|
+
...vJwtHeader.entries,
|
|
661
|
+
typ: v8.picklist(["application/at+jwt", "at+jwt"])
|
|
662
|
+
});
|
|
663
|
+
var vAccessTokenProfileJwtPayload = v8.looseObject({
|
|
664
|
+
...vJwtPayload.entries,
|
|
665
|
+
iss: v8.string(),
|
|
666
|
+
exp: vInteger2,
|
|
667
|
+
iat: vInteger2,
|
|
668
|
+
aud: v8.string(),
|
|
669
|
+
sub: v8.string(),
|
|
670
|
+
// REQUIRED according to RFC 9068, but OID4VCI allows anonymous access
|
|
671
|
+
client_id: v8.optional(v8.string()),
|
|
672
|
+
jti: v8.string(),
|
|
673
|
+
// SHOULD be included in the authorization request contained it
|
|
674
|
+
scope: v8.optional(v8.string())
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// src/access-token/verify-access-token.ts
|
|
678
|
+
var SupportedAuthenticationScheme = /* @__PURE__ */ ((SupportedAuthenticationScheme2) => {
|
|
679
|
+
SupportedAuthenticationScheme2["Bearer"] = "Bearer";
|
|
680
|
+
SupportedAuthenticationScheme2["DPoP"] = "DPoP";
|
|
681
|
+
return SupportedAuthenticationScheme2;
|
|
682
|
+
})(SupportedAuthenticationScheme || {});
|
|
683
|
+
async function verifyJwtProfileAccessToken(options) {
|
|
684
|
+
const decodedJwt = decodeJwt({
|
|
685
|
+
jwt: options.accessToken,
|
|
686
|
+
headerSchema: vAccessTokenProfileJwtHeader,
|
|
687
|
+
payloadSchema: vAccessTokenProfileJwtPayload
|
|
688
|
+
});
|
|
689
|
+
const authorizationServer = options.authorizationServers.find(({ issuer }) => decodedJwt.payload.iss === issuer);
|
|
690
|
+
if (!authorizationServer) {
|
|
691
|
+
throw new Oauth2Error(
|
|
692
|
+
`Access token jwt contains unrecognized authorization server 'iss' value of '${decodedJwt.payload.iss}'`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const jwks = await fetchJwks(authorizationServer, options.callbacks.fetch);
|
|
696
|
+
const publicJwk = extractJwkFromJwksForJwt({
|
|
697
|
+
kid: decodedJwt.header.kid,
|
|
698
|
+
jwks,
|
|
699
|
+
use: "sig"
|
|
700
|
+
});
|
|
701
|
+
await verifyJwt({
|
|
702
|
+
compact: options.accessToken,
|
|
703
|
+
header: decodedJwt.header,
|
|
704
|
+
payload: decodedJwt.payload,
|
|
705
|
+
signer: { method: "jwk", publicJwk, alg: decodedJwt.header.alg },
|
|
706
|
+
verifyJwtCallback: options.callbacks.verifyJwt,
|
|
707
|
+
errorMessage: "Error during verification of access token jwt.",
|
|
708
|
+
now: options.now,
|
|
709
|
+
expectedAudience: options.resourceServer
|
|
710
|
+
});
|
|
711
|
+
return {
|
|
712
|
+
header: decodedJwt.header,
|
|
713
|
+
payload: decodedJwt.payload,
|
|
714
|
+
authorizationServer
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/resource-request/make-resource-request.ts
|
|
719
|
+
import { defaultFetcher } from "@openid4vc/utils";
|
|
720
|
+
|
|
721
|
+
// src/dpop/dpop.ts
|
|
722
|
+
import {
|
|
723
|
+
URL,
|
|
724
|
+
dateToSeconds as dateToSeconds2,
|
|
725
|
+
decodeUtf8String as decodeUtf8String2,
|
|
726
|
+
encodeToBase64Url as encodeToBase64Url2,
|
|
727
|
+
parseWithErrorHandling as parseWithErrorHandling3
|
|
728
|
+
} from "@openid4vc/utils";
|
|
729
|
+
|
|
730
|
+
// src/dpop/v-dpop.ts
|
|
731
|
+
import * as v9 from "valibot";
|
|
732
|
+
import { vHttpMethod, vHttpsUrl as vHttpsUrl2, vInteger as vInteger3 } from "@openid4vc/utils";
|
|
733
|
+
var vDpopJwtPayload = v9.looseObject({
|
|
734
|
+
...vJwtPayload.entries,
|
|
735
|
+
iat: vInteger3,
|
|
736
|
+
htu: vHttpsUrl2,
|
|
737
|
+
htm: vHttpMethod,
|
|
738
|
+
jti: v9.string(),
|
|
739
|
+
// Only required when presenting in combination with access token
|
|
740
|
+
ath: v9.optional(v9.string())
|
|
741
|
+
});
|
|
742
|
+
var vDpopJwtHeader = v9.looseObject({
|
|
743
|
+
...vJwtHeader.entries,
|
|
744
|
+
typ: v9.literal("dpop+jwt"),
|
|
745
|
+
jwk: vJwk
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// src/dpop/dpop.ts
|
|
749
|
+
async function createDpopHeadersForRequest(options) {
|
|
750
|
+
const dpopJwt = await createDpopJwt(options);
|
|
751
|
+
return {
|
|
752
|
+
DPoP: dpopJwt
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
async function createDpopJwt(options) {
|
|
756
|
+
let ath = void 0;
|
|
757
|
+
if (options.accessToken) {
|
|
758
|
+
ath = encodeToBase64Url2(await options.callbacks.hash(decodeUtf8String2(options.accessToken), "SHA-256" /* Sha256 */));
|
|
759
|
+
}
|
|
760
|
+
const header = parseWithErrorHandling3(vDpopJwtHeader, {
|
|
761
|
+
typ: "dpop+jwt",
|
|
762
|
+
jwk: options.signer.publicJwk,
|
|
763
|
+
alg: options.signer.alg
|
|
764
|
+
});
|
|
765
|
+
const payload = parseWithErrorHandling3(vDpopJwtPayload, {
|
|
766
|
+
htu: htuFromRequestUrl(options.request.url),
|
|
767
|
+
iat: dateToSeconds2(options.issuedAt),
|
|
768
|
+
htm: options.request.method,
|
|
769
|
+
jti: encodeToBase64Url2(await options.callbacks.generateRandom(32)),
|
|
770
|
+
ath,
|
|
771
|
+
nonce: options.nonce,
|
|
772
|
+
...options.additionalPayload
|
|
773
|
+
});
|
|
774
|
+
const { jwt } = await options.callbacks.signJwt(options.signer, {
|
|
775
|
+
header,
|
|
776
|
+
payload
|
|
777
|
+
});
|
|
778
|
+
return jwt;
|
|
779
|
+
}
|
|
780
|
+
async function verifyDpopJwt(options) {
|
|
781
|
+
const { header, payload } = decodeJwt({
|
|
782
|
+
jwt: options.dpopJwt,
|
|
783
|
+
headerSchema: vDpopJwtHeader,
|
|
784
|
+
payloadSchema: vDpopJwtPayload
|
|
785
|
+
});
|
|
786
|
+
if (options.allowedSigningAlgs && !options.allowedSigningAlgs.includes(header.alg)) {
|
|
787
|
+
throw new Oauth2Error(
|
|
788
|
+
`dpop jwt uses alg value '${header.alg}' but allowed dpop signging alg values are ${options.allowedSigningAlgs.join(", ")}.`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
if (options.expectedNonce) {
|
|
792
|
+
if (!payload.nonce) {
|
|
793
|
+
throw new Oauth2Error(`Dpop jwt does not have a nonce value, but expected nonce value '${options.expectedNonce}'`);
|
|
794
|
+
}
|
|
795
|
+
if (payload.nonce !== options.expectedNonce) {
|
|
796
|
+
throw new Oauth2Error(
|
|
797
|
+
`Dpop jwt contains nonce value '${payload.nonce}', but expected nonce value '${options.expectedNonce}'`
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (options.request.method !== payload.htm) {
|
|
802
|
+
throw new Oauth2Error(
|
|
803
|
+
`Dpop jwt contains htm value '${payload.htm}', but expected htm value '${options.request.method}'`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
const expectedHtu = htuFromRequestUrl(options.request.url);
|
|
807
|
+
if (expectedHtu !== payload.htu) {
|
|
808
|
+
throw new Oauth2Error(`Dpop jwt contains htu value '${payload.htu}', but expected htu value '${expectedHtu}'.`);
|
|
809
|
+
}
|
|
810
|
+
if (options.accessToken) {
|
|
811
|
+
const expectedAth = encodeToBase64Url2(
|
|
812
|
+
await options.callbacks.hash(decodeUtf8String2(options.accessToken), "SHA-256" /* Sha256 */)
|
|
813
|
+
);
|
|
814
|
+
if (!payload.ath) {
|
|
815
|
+
throw new Oauth2Error(`Dpop jwt does not have a ath value, but expected ath value '${expectedAth}'.`);
|
|
816
|
+
}
|
|
817
|
+
if (payload.ath !== expectedAth) {
|
|
818
|
+
throw new Oauth2Error(`Dpop jwt contains ath value '${payload.ath}', but expected ath value '${expectedAth}'.`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (options.expectedJwkThumbprint) {
|
|
822
|
+
const jwkThumprint = await calculateJwkThumbprint({
|
|
823
|
+
hashAlgorithm: "SHA-256" /* Sha256 */,
|
|
824
|
+
hashCallback: options.callbacks.hash,
|
|
825
|
+
jwk: header.jwk
|
|
826
|
+
});
|
|
827
|
+
if (options.expectedJwkThumbprint !== jwkThumprint) {
|
|
828
|
+
throw new Oauth2Error(
|
|
829
|
+
`Dpop is signed with jwk with thumbprint value '${jwkThumprint}', but expect jwk thumbprint value '${options.expectedJwkThumbprint}'`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
await verifyJwt({
|
|
834
|
+
signer: {
|
|
835
|
+
alg: header.alg,
|
|
836
|
+
method: "jwk",
|
|
837
|
+
publicJwk: header.jwk
|
|
838
|
+
},
|
|
839
|
+
now: options.now,
|
|
840
|
+
header,
|
|
841
|
+
payload,
|
|
842
|
+
compact: options.dpopJwt,
|
|
843
|
+
verifyJwtCallback: options.callbacks.verifyJwt,
|
|
844
|
+
errorMessage: "dpop jwt verification failed"
|
|
845
|
+
});
|
|
846
|
+
return {
|
|
847
|
+
header,
|
|
848
|
+
payload
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function htuFromRequestUrl(requestUrl) {
|
|
852
|
+
const htu = new URL(requestUrl);
|
|
853
|
+
htu.search = "";
|
|
854
|
+
htu.hash = "";
|
|
855
|
+
return htu.toString();
|
|
856
|
+
}
|
|
857
|
+
function extractDpopNonceFromHeaders(headers) {
|
|
858
|
+
return headers.get("DPoP-Nonce");
|
|
859
|
+
}
|
|
860
|
+
function extractDpopJwtFromHeaders(headers) {
|
|
861
|
+
const dpopJwt = headers.get("DPoP");
|
|
862
|
+
if (dpopJwt && (typeof dpopJwt !== "string" || dpopJwt.includes(","))) {
|
|
863
|
+
return { valid: false };
|
|
864
|
+
}
|
|
865
|
+
return { valid: true, dpopJwt: dpopJwt ?? void 0 };
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/dpop/dpop-retry.ts
|
|
869
|
+
async function authorizationServerRequestWithDpopRetry(options) {
|
|
870
|
+
try {
|
|
871
|
+
return await options.request(options.dpop);
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (options.dpop && error instanceof Oauth2ClientErrorResponseError) {
|
|
874
|
+
const dpopRetry = shouldRetryAuthorizationServerRequestWithDPoPNonce({
|
|
875
|
+
responseHeaders: error.response.headers,
|
|
876
|
+
errorResponse: error.errorResponse
|
|
877
|
+
});
|
|
878
|
+
if (dpopRetry.retry) {
|
|
879
|
+
return options.request({
|
|
880
|
+
...options.dpop,
|
|
881
|
+
nonce: dpopRetry.dpopNonce
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
throw error;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function shouldRetryAuthorizationServerRequestWithDPoPNonce(options) {
|
|
889
|
+
if (options.errorResponse.error !== "use_dpop_nonce") {
|
|
890
|
+
return {
|
|
891
|
+
retry: false
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
const dpopNonce = extractDpopNonceFromHeaders(options.responseHeaders);
|
|
895
|
+
if (!dpopNonce) {
|
|
896
|
+
throw new Oauth2Error(
|
|
897
|
+
`Error response error contains error 'use_dpop_nonce' but the response headers do not include a valid 'DPoP-Nonce' header value.`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
retry: true,
|
|
902
|
+
dpopNonce
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function shouldRetryResourceRequestWithDPoPNonce(options) {
|
|
906
|
+
const useDpopNonceChallenge = options.resourceUnauthorizedError.wwwAuthenticateHeaders.find(
|
|
907
|
+
(challenge) => challenge.scheme === "DPoP" /* DPoP */ && challenge.error === "use_dpop_nonce" /* UseDpopNonce */
|
|
908
|
+
);
|
|
909
|
+
if (!useDpopNonceChallenge) {
|
|
910
|
+
return { retry: false };
|
|
911
|
+
}
|
|
912
|
+
const dpopNonce = extractDpopNonceFromHeaders(options.responseHeaders);
|
|
913
|
+
if (!dpopNonce || typeof dpopNonce !== "string") {
|
|
914
|
+
throw new Oauth2Error(
|
|
915
|
+
`Resource request error in 'WWW-Authenticate' response header contains error 'use_dpop_nonce' but the response headers do not include a valid 'DPoP-Nonce' value.`
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
retry: true,
|
|
920
|
+
dpopNonce
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/resource-request/make-resource-request.ts
|
|
925
|
+
async function resourceRequest(options) {
|
|
926
|
+
const dpopHeaders = options.dpop ? await createDpopHeadersForRequest({
|
|
927
|
+
request: {
|
|
928
|
+
url: options.url,
|
|
929
|
+
// in fetch the default is GET if not provided
|
|
930
|
+
method: options.requestOptions.method ?? "GET"
|
|
931
|
+
},
|
|
932
|
+
signer: options.dpop.signer,
|
|
933
|
+
callbacks: options.callbacks,
|
|
934
|
+
nonce: options.dpop.nonce,
|
|
935
|
+
accessToken: options.accessToken
|
|
936
|
+
}) : void 0;
|
|
937
|
+
const fetch = options.callbacks.fetch ?? defaultFetcher;
|
|
938
|
+
const response = await fetch(options.url, {
|
|
939
|
+
...options.requestOptions,
|
|
940
|
+
headers: {
|
|
941
|
+
...options.requestOptions.headers,
|
|
942
|
+
Authorization: `${dpopHeaders ? "DPoP" : "Bearer"} ${options.accessToken}`,
|
|
943
|
+
...dpopHeaders
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
const dpopNonce = extractDpopNonceFromHeaders(response.headers);
|
|
947
|
+
if (response.ok) {
|
|
948
|
+
return {
|
|
949
|
+
ok: true,
|
|
950
|
+
response,
|
|
951
|
+
dpop: dpopNonce ? {
|
|
952
|
+
nonce: dpopNonce
|
|
953
|
+
} : void 0
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
const wwwAuthenticateHeader = response.headers.get("WWW-Authenticate");
|
|
957
|
+
const resourceUnauthorizedError = wwwAuthenticateHeader ? Oauth2ResourceUnauthorizedError.fromHeaderValue(wwwAuthenticateHeader) : void 0;
|
|
958
|
+
const shouldRetryWithNonce = options.dpop?.retryWithNonce ?? true;
|
|
959
|
+
const dpopRetry = resourceUnauthorizedError ? shouldRetryResourceRequestWithDPoPNonce({
|
|
960
|
+
responseHeaders: response.headers,
|
|
961
|
+
resourceUnauthorizedError
|
|
962
|
+
}) : void 0;
|
|
963
|
+
if (shouldRetryWithNonce && dpopRetry?.retry && options.dpop) {
|
|
964
|
+
return await resourceRequest({
|
|
965
|
+
...options,
|
|
966
|
+
dpop: {
|
|
967
|
+
...options.dpop,
|
|
968
|
+
nonce: dpopRetry.dpopNonce,
|
|
969
|
+
// We'll never try multiple times (to prevent endless recursion)
|
|
970
|
+
retryWithNonce: false
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
ok: false,
|
|
976
|
+
response,
|
|
977
|
+
dpop: dpopNonce ? {
|
|
978
|
+
nonce: dpopNonce
|
|
979
|
+
} : void 0,
|
|
980
|
+
wwwAuthenticate: resourceUnauthorizedError?.wwwAuthenticateHeaders
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/resource-request/verify-resource-request.ts
|
|
985
|
+
import { ValidationError as ValidationError2 } from "@openid4vc/utils";
|
|
986
|
+
|
|
987
|
+
// src/access-token/introspect-token.ts
|
|
988
|
+
import { ContentType as ContentType3, createValibotFetcher as createValibotFetcher3, objectToQueryParams, parseWithErrorHandling as parseWithErrorHandling4 } from "@openid4vc/utils";
|
|
989
|
+
import { InvalidFetchResponseError as InvalidFetchResponseError3 } from "@openid4vc/utils";
|
|
990
|
+
import { Headers } from "@openid4vc/utils";
|
|
991
|
+
|
|
992
|
+
// src/access-token/v-token-introspection.ts
|
|
993
|
+
import { vInteger as vInteger4 } from "@openid4vc/utils";
|
|
994
|
+
import * as v10 from "valibot";
|
|
995
|
+
var vTokenIntrospectionRequest = v10.looseObject({
|
|
996
|
+
token: v10.string(),
|
|
997
|
+
token_type_hint: v10.optional(v10.string())
|
|
998
|
+
});
|
|
999
|
+
var vTokenIntrospectionResponse = v10.looseObject({
|
|
1000
|
+
active: v10.boolean(),
|
|
1001
|
+
scope: v10.optional(v10.string()),
|
|
1002
|
+
client_id: v10.optional(v10.string()),
|
|
1003
|
+
username: v10.optional(v10.string()),
|
|
1004
|
+
token_type: v10.optional(v10.string()),
|
|
1005
|
+
exp: v10.optional(vInteger4),
|
|
1006
|
+
iat: v10.optional(vInteger4),
|
|
1007
|
+
nbf: v10.optional(vInteger4),
|
|
1008
|
+
sub: v10.optional(v10.string()),
|
|
1009
|
+
aud: v10.optional(v10.string()),
|
|
1010
|
+
iss: v10.optional(v10.string()),
|
|
1011
|
+
jti: v10.optional(v10.string()),
|
|
1012
|
+
cnf: v10.optional(vJwtConfirmationPayload)
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
// src/access-token/introspect-token.ts
|
|
1016
|
+
async function introspectToken(options) {
|
|
1017
|
+
const fetchWithValibot = createValibotFetcher3(options.callbacks.fetch);
|
|
1018
|
+
const introspectionRequest = parseWithErrorHandling4(vTokenIntrospectionRequest, {
|
|
1019
|
+
token: options.token,
|
|
1020
|
+
token_type_hint: options.tokenTypeHint,
|
|
1021
|
+
...options.additionalPayload
|
|
1022
|
+
});
|
|
1023
|
+
const introspectionEndpoint = options.authorizationServerMetadata.introspection_endpoint;
|
|
1024
|
+
if (!introspectionEndpoint) {
|
|
1025
|
+
throw new Oauth2Error(`Missing required 'introspection_endpoint' parameter in authorization server metadata`);
|
|
1026
|
+
}
|
|
1027
|
+
const headers = new Headers({
|
|
1028
|
+
"Content-Type": ContentType3.XWwwFormUrlencoded
|
|
1029
|
+
});
|
|
1030
|
+
await options.callbacks.clientAuthentication({
|
|
1031
|
+
url: introspectionEndpoint,
|
|
1032
|
+
method: "POST",
|
|
1033
|
+
authorizationServerMetata: options.authorizationServerMetadata,
|
|
1034
|
+
body: introspectionRequest,
|
|
1035
|
+
contentType: ContentType3.XWwwFormUrlencoded,
|
|
1036
|
+
headers
|
|
1037
|
+
});
|
|
1038
|
+
const { result, response } = await fetchWithValibot(
|
|
1039
|
+
vTokenIntrospectionResponse,
|
|
1040
|
+
ContentType3.Json,
|
|
1041
|
+
introspectionEndpoint,
|
|
1042
|
+
{
|
|
1043
|
+
body: objectToQueryParams(introspectionRequest).toString(),
|
|
1044
|
+
method: "POST",
|
|
1045
|
+
headers
|
|
1046
|
+
}
|
|
1047
|
+
);
|
|
1048
|
+
if (!response.ok || !result?.success) {
|
|
1049
|
+
throw new InvalidFetchResponseError3(
|
|
1050
|
+
`Unable to introspect token from '${introspectionEndpoint}'. Received response with status ${response.status}`,
|
|
1051
|
+
await response.clone().text(),
|
|
1052
|
+
response
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
return result.output;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/resource-request/verify-resource-request.ts
|
|
1059
|
+
async function verifyResourceRequest(options) {
|
|
1060
|
+
const allowedAuthenticationSchemes = options.allowedAuthenticationSchemes ?? Object.values(SupportedAuthenticationScheme);
|
|
1061
|
+
if (allowedAuthenticationSchemes.length === 0) {
|
|
1062
|
+
throw new Oauth2Error(
|
|
1063
|
+
`Emtpy array provided for 'allowedAuthenticationSchemes', provide at least one allowed authentication scheme, or remove the value to allow all supported authentication schemes`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
const authorizationHeader = options.request.headers.get("Authorization");
|
|
1067
|
+
if (!authorizationHeader) {
|
|
1068
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1069
|
+
`No 'Authorization' header provided in request.`,
|
|
1070
|
+
allowedAuthenticationSchemes.map((scheme2) => ({ scheme: scheme2 }))
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const [scheme, accessToken] = authorizationHeader.split(" ", 2);
|
|
1074
|
+
if (!scheme || !accessToken) {
|
|
1075
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1076
|
+
`Malformed 'Authorization' header provided in request.`,
|
|
1077
|
+
allowedAuthenticationSchemes.map((scheme2) => ({ scheme: scheme2 }))
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (!allowedAuthenticationSchemes.includes(scheme) || scheme !== "Bearer" /* Bearer */ && scheme !== "DPoP" /* DPoP */) {
|
|
1081
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1082
|
+
`Provided authentication scheme '${scheme}' is not allowed. Allowed authentication schemes are ${allowedAuthenticationSchemes.map((s) => `'${s}'`).join(", ")}.`,
|
|
1083
|
+
allowedAuthenticationSchemes.map((scheme2) => ({ scheme: scheme2 }))
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
const verificationResult = await verifyJwtProfileAccessToken({
|
|
1087
|
+
accessToken,
|
|
1088
|
+
callbacks: options.callbacks,
|
|
1089
|
+
authorizationServers: options.authorizationServers,
|
|
1090
|
+
resourceServer: options.resourceServer,
|
|
1091
|
+
now: options.now
|
|
1092
|
+
}).catch((error) => {
|
|
1093
|
+
if (error instanceof Oauth2JwtParseError || error instanceof ValidationError2) return null;
|
|
1094
|
+
const errorMessage = error instanceof Oauth2Error ? error.message : "Invalid access token";
|
|
1095
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1096
|
+
`Error occured during verification of jwt profile access token: ${error.message}`,
|
|
1097
|
+
{
|
|
1098
|
+
scheme,
|
|
1099
|
+
error: "invalid_token" /* InvalidToken */,
|
|
1100
|
+
error_description: errorMessage
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
});
|
|
1104
|
+
let tokenPayload = verificationResult?.payload;
|
|
1105
|
+
let authorizationServer = verificationResult?.authorizationServer;
|
|
1106
|
+
if (!tokenPayload) {
|
|
1107
|
+
for (const authorizationServerMetadata of options.authorizationServers) {
|
|
1108
|
+
try {
|
|
1109
|
+
tokenPayload = await introspectToken({
|
|
1110
|
+
authorizationServerMetadata,
|
|
1111
|
+
callbacks: options.callbacks,
|
|
1112
|
+
token: accessToken,
|
|
1113
|
+
tokenTypeHint: scheme
|
|
1114
|
+
});
|
|
1115
|
+
authorizationServer = authorizationServerMetadata;
|
|
1116
|
+
if (tokenPayload.active) break;
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (!tokenPayload || !authorizationServer) {
|
|
1122
|
+
throw new Oauth2ResourceUnauthorizedError("Could not verify token as jwt or using token introspection.", {
|
|
1123
|
+
scheme,
|
|
1124
|
+
error: "invalid_token" /* InvalidToken */,
|
|
1125
|
+
error_description: "Token is not valid"
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
let dpopJwk = void 0;
|
|
1129
|
+
if (scheme === "DPoP" /* DPoP */ || // two alternative methods to determine whether DPoP was used. As the user can
|
|
1130
|
+
// choose to include `Bearer` scheme even if DPoP was used
|
|
1131
|
+
tokenPayload.token_type === "DPoP" /* DPoP */ || tokenPayload.cnf?.jkt) {
|
|
1132
|
+
const dpopJwtResult = extractDpopJwtFromHeaders(options.request.headers);
|
|
1133
|
+
if (!dpopJwtResult.valid) {
|
|
1134
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1135
|
+
`Request contains a 'DPoP' header, but the value is not a valid DPoP jwt.`,
|
|
1136
|
+
{
|
|
1137
|
+
scheme,
|
|
1138
|
+
error: "invalid_dpop_proof" /* InvalidDpopProof */,
|
|
1139
|
+
error_description: `Request contains a 'DPoP' header, but the value is not a valid DPoP jwt.`
|
|
1140
|
+
}
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (!dpopJwtResult.dpopJwt) {
|
|
1144
|
+
throw new Oauth2ResourceUnauthorizedError(`Request is missing required 'DPoP' header.`, {
|
|
1145
|
+
scheme,
|
|
1146
|
+
error: "invalid_dpop_proof" /* InvalidDpopProof */,
|
|
1147
|
+
error_description: `Request is missing required 'DPoP' header.`
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
if (!tokenPayload.cnf?.jkt) {
|
|
1151
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1152
|
+
`Token payload is missing required 'cnf.jkt' value for DPoP verification.`,
|
|
1153
|
+
{
|
|
1154
|
+
scheme,
|
|
1155
|
+
error: "invalid_token" /* InvalidToken */,
|
|
1156
|
+
error_description: `Token payload is missing required 'cnf.jkt' value for DPoP verification.`
|
|
1157
|
+
}
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
const decodedDpopJwt = await verifyDpopJwt({
|
|
1162
|
+
callbacks: options.callbacks,
|
|
1163
|
+
dpopJwt: dpopJwtResult.dpopJwt,
|
|
1164
|
+
request: options.request,
|
|
1165
|
+
accessToken,
|
|
1166
|
+
now: options.now,
|
|
1167
|
+
expectedJwkThumbprint: tokenPayload.cnf?.jkt,
|
|
1168
|
+
allowedSigningAlgs: authorizationServer.dpop_signing_alg_values_supported
|
|
1169
|
+
});
|
|
1170
|
+
dpopJwk = decodedDpopJwt.header.jwk;
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
const errorMessage = error instanceof Oauth2Error ? error.message : "Error verifying DPoP jwt";
|
|
1173
|
+
throw new Oauth2ResourceUnauthorizedError(
|
|
1174
|
+
`Error occured during verification of jwt profile access token: ${error instanceof Error ? error.message : error}`,
|
|
1175
|
+
{
|
|
1176
|
+
scheme,
|
|
1177
|
+
error: "invalid_dpop_proof" /* InvalidDpopProof */,
|
|
1178
|
+
error_description: errorMessage
|
|
1179
|
+
}
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
tokenPayload,
|
|
1185
|
+
dpopJwk,
|
|
1186
|
+
scheme,
|
|
1187
|
+
accessToken,
|
|
1188
|
+
authorizationServer: authorizationServer.issuer
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/client-authentication.ts
|
|
1193
|
+
import { decodeUtf8String as decodeUtf8String3, encodeToBase64Url as encodeToBase64Url3 } from "@openid4vc/utils";
|
|
1194
|
+
var SupportedClientAuthenticationMethod = /* @__PURE__ */ ((SupportedClientAuthenticationMethod2) => {
|
|
1195
|
+
SupportedClientAuthenticationMethod2["ClientSecretBasic"] = "client_secret_basic";
|
|
1196
|
+
SupportedClientAuthenticationMethod2["ClientSecretPost"] = "client_secret_post";
|
|
1197
|
+
return SupportedClientAuthenticationMethod2;
|
|
1198
|
+
})(SupportedClientAuthenticationMethod || {});
|
|
1199
|
+
function getSupportedClientAuthenticationMethod(authorizationServer, endpointType) {
|
|
1200
|
+
if (endpointType === "introspection" && authorizationServer.introspection_endpoint_auth_methods_supported) {
|
|
1201
|
+
const supportedMethod = authorizationServer.introspection_endpoint_auth_methods_supported.find(
|
|
1202
|
+
(m) => Object.values(SupportedClientAuthenticationMethod).includes(m)
|
|
1203
|
+
);
|
|
1204
|
+
if (!supportedMethod) {
|
|
1205
|
+
throw new Oauth2Error(
|
|
1206
|
+
`Authorization server metadata for issuer '${authorizationServer.issuer}' has 'introspection_endpoint_auth_methods_supported' metadata, but does not contain a supported value. Supported values are '${Object.values(
|
|
1207
|
+
SupportedClientAuthenticationMethod
|
|
1208
|
+
).join(
|
|
1209
|
+
", "
|
|
1210
|
+
)}', found values are '${authorizationServer.introspection_endpoint_auth_methods_supported.join(", ")}'`
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
return supportedMethod;
|
|
1214
|
+
}
|
|
1215
|
+
if (authorizationServer.token_endpoint_auth_methods_supported) {
|
|
1216
|
+
const supportedMethod = authorizationServer.token_endpoint_auth_methods_supported.find(
|
|
1217
|
+
(m) => Object.values(SupportedClientAuthenticationMethod).includes(m)
|
|
1218
|
+
);
|
|
1219
|
+
if (!supportedMethod) {
|
|
1220
|
+
throw new Oauth2Error(
|
|
1221
|
+
`Authorization server metadata for issuer '${authorizationServer.issuer}' has 'token_endpoint_auth_methods_supported' metadata, but does not contain a supported value. Supported values are '${Object.values(
|
|
1222
|
+
SupportedClientAuthenticationMethod
|
|
1223
|
+
).join(", ")}', found values are '${authorizationServer.token_endpoint_auth_methods_supported.join(", ")}'`
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
return supportedMethod;
|
|
1227
|
+
}
|
|
1228
|
+
return "client_secret_basic" /* ClientSecretBasic */;
|
|
1229
|
+
}
|
|
1230
|
+
function clientAuthenticationDynamic(options) {
|
|
1231
|
+
return (callbackOptions) => {
|
|
1232
|
+
const { url: url3, authorizationServerMetata } = callbackOptions;
|
|
1233
|
+
const endpointType = url3 === authorizationServerMetata.introspection_endpoint ? "introspection" : "endpoint";
|
|
1234
|
+
const method = getSupportedClientAuthenticationMethod(authorizationServerMetata, endpointType);
|
|
1235
|
+
if (method === "client_secret_basic" /* ClientSecretBasic */) {
|
|
1236
|
+
return clientAuthenticationClientSecretBasic(options)(callbackOptions);
|
|
1237
|
+
}
|
|
1238
|
+
if (method === "client_secret_post" /* ClientSecretPost */) {
|
|
1239
|
+
return clientAuthenticationClientSecretPost(options)(callbackOptions);
|
|
1240
|
+
}
|
|
1241
|
+
throw new Oauth2Error(
|
|
1242
|
+
`Unsupported client auth method ${method}. Supported values are ${Object.values(
|
|
1243
|
+
SupportedClientAuthenticationMethod
|
|
1244
|
+
).join(", ")}`
|
|
1245
|
+
);
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function clientAuthenticationClientSecretPost(options) {
|
|
1249
|
+
return ({ body }) => {
|
|
1250
|
+
body.client_id = options.clientId;
|
|
1251
|
+
body.client_secret = options.clientSecret;
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
function clientAuthenticationClientSecretBasic(options) {
|
|
1255
|
+
return ({ headers }) => {
|
|
1256
|
+
const authorization = encodeToBase64Url3(decodeUtf8String3(`${options.clientId}:${options.clientSecret}`));
|
|
1257
|
+
headers.set("Authorization", `Basic ${authorization}`);
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function clientAuthenticationNone() {
|
|
1261
|
+
return () => {
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/Oauth2AuthorizationServer.ts
|
|
1266
|
+
import { parseWithErrorHandling as parseWithErrorHandling11 } from "@openid4vc/utils";
|
|
1267
|
+
|
|
1268
|
+
// src/access-token/create-access-token.ts
|
|
1269
|
+
import { addSecondsToDate, dateToSeconds as dateToSeconds3, encodeToBase64Url as encodeToBase64Url4, parseWithErrorHandling as parseWithErrorHandling5 } from "@openid4vc/utils";
|
|
1270
|
+
async function createAccessTokenJwt(options) {
|
|
1271
|
+
const header = parseWithErrorHandling5(vAccessTokenProfileJwtHeader, {
|
|
1272
|
+
...jwtHeaderFromJwtSigner(options.signer),
|
|
1273
|
+
typ: "at+jwt"
|
|
1274
|
+
});
|
|
1275
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
1276
|
+
const payload = parseWithErrorHandling5(vAccessTokenProfileJwtPayload, {
|
|
1277
|
+
iat: dateToSeconds3(now),
|
|
1278
|
+
exp: dateToSeconds3(addSecondsToDate(now, options.expiresInSeconds)),
|
|
1279
|
+
aud: options.audience,
|
|
1280
|
+
iss: options.authorizationServer,
|
|
1281
|
+
jti: encodeToBase64Url4(await options.callbacks.generateRandom(32)),
|
|
1282
|
+
client_id: options.clientId,
|
|
1283
|
+
sub: options.subject,
|
|
1284
|
+
scope: options.scope,
|
|
1285
|
+
cnf: options.dpopJwk ? {
|
|
1286
|
+
jkt: await calculateJwkThumbprint({
|
|
1287
|
+
hashAlgorithm: "SHA-256" /* Sha256 */,
|
|
1288
|
+
hashCallback: options.callbacks.hash,
|
|
1289
|
+
jwk: options.dpopJwk
|
|
1290
|
+
})
|
|
1291
|
+
} : void 0,
|
|
1292
|
+
...options.additionalPayload
|
|
1293
|
+
});
|
|
1294
|
+
const { jwt } = await options.callbacks.signJwt(options.signer, {
|
|
1295
|
+
header,
|
|
1296
|
+
payload
|
|
1297
|
+
});
|
|
1298
|
+
return {
|
|
1299
|
+
jwt
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/access-token/create-access-token-response.ts
|
|
1304
|
+
import { parseWithErrorHandling as parseWithErrorHandling6 } from "@openid4vc/utils";
|
|
1305
|
+
|
|
1306
|
+
// src/access-token/v-access-token.ts
|
|
1307
|
+
import * as v12 from "valibot";
|
|
1308
|
+
import { vHttpsUrl as vHttpsUrl3 } from "@openid4vc/utils";
|
|
1309
|
+
|
|
1310
|
+
// src/v-grant-type.ts
|
|
1311
|
+
import * as v11 from "valibot";
|
|
1312
|
+
var vPreAuthorizedCodeGrantIdentifier = v11.literal("urn:ietf:params:oauth:grant-type:pre-authorized_code");
|
|
1313
|
+
var preAuthorizedCodeGrantIdentifier = vPreAuthorizedCodeGrantIdentifier.literal;
|
|
1314
|
+
var vAuthorizationCodeGrantIdentifier = v11.literal("authorization_code");
|
|
1315
|
+
var authorizationCodeGrantIdentifier = vAuthorizationCodeGrantIdentifier.literal;
|
|
1316
|
+
var vRefreshTokenGrantIdentifier = v11.literal("refresh_token");
|
|
1317
|
+
var refreshTokenGrantIdentifier = vRefreshTokenGrantIdentifier.literal;
|
|
1318
|
+
|
|
1319
|
+
// src/access-token/v-access-token.ts
|
|
1320
|
+
var vAccessTokenRequest = v12.intersect([
|
|
1321
|
+
v12.looseObject({
|
|
1322
|
+
// Pre authorized code flow
|
|
1323
|
+
"pre-authorized_code": v12.optional(v12.string()),
|
|
1324
|
+
// Authorization code flow
|
|
1325
|
+
code: v12.optional(v12.string()),
|
|
1326
|
+
redirect_uri: v12.optional(v12.pipe(v12.string(), v12.url())),
|
|
1327
|
+
// Refresh token grant
|
|
1328
|
+
refresh_token: v12.optional(v12.string()),
|
|
1329
|
+
resource: v12.optional(vHttpsUrl3),
|
|
1330
|
+
code_verifier: v12.optional(v12.string()),
|
|
1331
|
+
grant_type: v12.union([
|
|
1332
|
+
vPreAuthorizedCodeGrantIdentifier,
|
|
1333
|
+
vAuthorizationCodeGrantIdentifier,
|
|
1334
|
+
vRefreshTokenGrantIdentifier,
|
|
1335
|
+
// string makes the previous ones unessary, but it does help with error messages
|
|
1336
|
+
v12.string()
|
|
1337
|
+
])
|
|
1338
|
+
}),
|
|
1339
|
+
v12.pipe(
|
|
1340
|
+
v12.looseObject({
|
|
1341
|
+
tx_code: v12.optional(v12.string()),
|
|
1342
|
+
// user_pin is from OID4VCI draft 11
|
|
1343
|
+
user_pin: v12.optional(v12.string())
|
|
1344
|
+
}),
|
|
1345
|
+
// Check that user_pin and tx_code are the same if both are provided
|
|
1346
|
+
// and transform user_pin to tx_code if only user_pin is provided
|
|
1347
|
+
v12.check(
|
|
1348
|
+
({ tx_code, user_pin }) => !tx_code || !user_pin || user_pin === tx_code,
|
|
1349
|
+
`If both 'tx_code' and 'user_pin' are present they must match`
|
|
1350
|
+
),
|
|
1351
|
+
v12.transform(({ tx_code, user_pin, ...rest }) => {
|
|
1352
|
+
return {
|
|
1353
|
+
...rest,
|
|
1354
|
+
...tx_code ?? user_pin ? { tx_code: tx_code ?? user_pin } : {}
|
|
1355
|
+
};
|
|
1356
|
+
})
|
|
1357
|
+
)
|
|
1358
|
+
]);
|
|
1359
|
+
var vAccessTokenResponse = v12.looseObject({
|
|
1360
|
+
access_token: v12.string(),
|
|
1361
|
+
token_type: v12.string(),
|
|
1362
|
+
expires_in: v12.optional(v12.pipe(v12.number(), v12.integer())),
|
|
1363
|
+
scope: v12.optional(v12.string()),
|
|
1364
|
+
state: v12.optional(v12.string()),
|
|
1365
|
+
refresh_token: v12.optional(v12.string()),
|
|
1366
|
+
// Oid4vci specific parameters
|
|
1367
|
+
c_nonce: v12.optional(v12.string()),
|
|
1368
|
+
c_nonce_expires_in: v12.optional(v12.pipe(v12.number(), v12.integer())),
|
|
1369
|
+
// TODO: add additional params
|
|
1370
|
+
authorization_details: v12.optional(
|
|
1371
|
+
v12.array(
|
|
1372
|
+
v12.looseObject({
|
|
1373
|
+
// requried when type is openid_credential (so we probably need a discriminator)
|
|
1374
|
+
// credential_identifiers: v.array(v.string()),
|
|
1375
|
+
})
|
|
1376
|
+
)
|
|
1377
|
+
)
|
|
1378
|
+
});
|
|
1379
|
+
var vAccessTokenErrorResponse = vOauth2ErrorResponse;
|
|
1380
|
+
|
|
1381
|
+
// src/access-token/create-access-token-response.ts
|
|
1382
|
+
async function createAccessTokenResponse(options) {
|
|
1383
|
+
const accessTokenResponse = parseWithErrorHandling6(vAccessTokenResponse, {
|
|
1384
|
+
access_token: options.accessToken,
|
|
1385
|
+
token_type: options.tokenType,
|
|
1386
|
+
expires_in: options.expiresInSeconds,
|
|
1387
|
+
c_nonce: options.cNonce,
|
|
1388
|
+
c_nonce_expires_in: options.cNonceExpiresIn,
|
|
1389
|
+
...options.additionalPayload
|
|
1390
|
+
});
|
|
1391
|
+
return accessTokenResponse;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/access-token/parse-access-token-request.ts
|
|
1395
|
+
import { valibotRecursiveFlattenIssues as valibotRecursiveFlattenIssues2 } from "@openid4vc/utils";
|
|
1396
|
+
import * as v13 from "valibot";
|
|
1397
|
+
function parseAccessTokenRequest(options) {
|
|
1398
|
+
const parsedAccessTokenRequest = v13.safeParse(vAccessTokenRequest, options.accessTokenRequest);
|
|
1399
|
+
if (!parsedAccessTokenRequest.success) {
|
|
1400
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1401
|
+
error: "invalid_request" /* InvalidRequest */,
|
|
1402
|
+
error_description: `Error occured during validation of authorization request.
|
|
1403
|
+
${JSON.stringify(valibotRecursiveFlattenIssues2(parsedAccessTokenRequest.issues), null, 2)}`
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
const accessTokenRequest = parsedAccessTokenRequest.output;
|
|
1407
|
+
let grant;
|
|
1408
|
+
if (accessTokenRequest.grant_type === preAuthorizedCodeGrantIdentifier) {
|
|
1409
|
+
if (!accessTokenRequest["pre-authorized_code"]) {
|
|
1410
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1411
|
+
error: "invalid_request" /* InvalidRequest */,
|
|
1412
|
+
error_description: `Missing required 'pre-authorized_code' for grant type '${preAuthorizedCodeGrantIdentifier}'`
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
grant = {
|
|
1416
|
+
grantType: preAuthorizedCodeGrantIdentifier,
|
|
1417
|
+
preAuthorizedCode: accessTokenRequest["pre-authorized_code"],
|
|
1418
|
+
txCode: accessTokenRequest.tx_code
|
|
1419
|
+
};
|
|
1420
|
+
} else if (accessTokenRequest.grant_type === authorizationCodeGrantIdentifier) {
|
|
1421
|
+
if (!accessTokenRequest.code) {
|
|
1422
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1423
|
+
error: "invalid_request" /* InvalidRequest */,
|
|
1424
|
+
error_description: `Missing required 'code' for grant type '${authorizationCodeGrantIdentifier}'`
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
grant = {
|
|
1428
|
+
grantType: authorizationCodeGrantIdentifier,
|
|
1429
|
+
code: accessTokenRequest.code
|
|
1430
|
+
};
|
|
1431
|
+
} else {
|
|
1432
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1433
|
+
error: "unsupported_grant_type" /* UnsupportedGrantType */,
|
|
1434
|
+
error_description: `The grant type '${accessTokenRequest.grant_type}' is not supported`
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
const extractedDpopJwt = extractDpopJwtFromHeaders(options.request.headers);
|
|
1438
|
+
if (!extractedDpopJwt.valid) {
|
|
1439
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1440
|
+
error: "invalid_dpop_proof" /* InvalidDpopProof */,
|
|
1441
|
+
error_description: `Request contains a 'DPoP' header, but the value is not a valid DPoP jwt`
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
const pkceCodeVerifier = accessTokenRequest.code_verifier;
|
|
1445
|
+
return {
|
|
1446
|
+
accessTokenRequest,
|
|
1447
|
+
grant,
|
|
1448
|
+
dpopJwt: extractedDpopJwt.dpopJwt,
|
|
1449
|
+
pkceCodeVerifier
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// src/pkce.ts
|
|
1454
|
+
import { decodeUtf8String as decodeUtf8String4, encodeToBase64Url as encodeToBase64Url5 } from "@openid4vc/utils";
|
|
1455
|
+
var PkceCodeChallengeMethod = /* @__PURE__ */ ((PkceCodeChallengeMethod2) => {
|
|
1456
|
+
PkceCodeChallengeMethod2["Plain"] = "plain";
|
|
1457
|
+
PkceCodeChallengeMethod2["S256"] = "S256";
|
|
1458
|
+
return PkceCodeChallengeMethod2;
|
|
1459
|
+
})(PkceCodeChallengeMethod || {});
|
|
1460
|
+
async function createPkce(options) {
|
|
1461
|
+
const allowedCodeChallengeMethods = options.allowedCodeChallengeMethods ?? [
|
|
1462
|
+
"S256" /* S256 */,
|
|
1463
|
+
"plain" /* Plain */
|
|
1464
|
+
];
|
|
1465
|
+
if (allowedCodeChallengeMethods.length === 0) {
|
|
1466
|
+
throw new Oauth2Error(`Unable to create PKCE code verifier. 'allowedCodeChallengeMethods' is an empty array.`);
|
|
1467
|
+
}
|
|
1468
|
+
const codeChallengeMethod = allowedCodeChallengeMethods.includes("S256" /* S256 */) ? "S256" /* S256 */ : "plain" /* Plain */;
|
|
1469
|
+
const codeVerifier = options.codeVerifier ?? encodeToBase64Url5(await options.callbacks.generateRandom(64));
|
|
1470
|
+
return {
|
|
1471
|
+
codeVerifier,
|
|
1472
|
+
codeChallenge: await calculateCodeChallenge({
|
|
1473
|
+
codeChallengeMethod,
|
|
1474
|
+
codeVerifier,
|
|
1475
|
+
hashCallback: options.callbacks.hash
|
|
1476
|
+
}),
|
|
1477
|
+
codeChallengeMethod
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
async function verifyPkce(options) {
|
|
1481
|
+
const calculatedCodeChallenge = await calculateCodeChallenge({
|
|
1482
|
+
codeChallengeMethod: options.codeChallengeMethod,
|
|
1483
|
+
codeVerifier: options.codeVerifier,
|
|
1484
|
+
hashCallback: options.callbacks.hash
|
|
1485
|
+
});
|
|
1486
|
+
if (options.codeChallenge !== calculatedCodeChallenge) {
|
|
1487
|
+
throw new Oauth2Error(
|
|
1488
|
+
`Derived code challenge '${calculatedCodeChallenge}' from code_verifier '${options.codeVerifier}' using code challenge method '${options.codeChallengeMethod}' does not match the expected code challenge.`
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
async function calculateCodeChallenge(options) {
|
|
1493
|
+
if (options.codeChallengeMethod === "plain" /* Plain */) {
|
|
1494
|
+
return options.codeVerifier;
|
|
1495
|
+
}
|
|
1496
|
+
if (options.codeChallengeMethod === "S256" /* S256 */) {
|
|
1497
|
+
return encodeToBase64Url5(await options.hashCallback(decodeUtf8String4(options.codeVerifier), "SHA-256" /* Sha256 */));
|
|
1498
|
+
}
|
|
1499
|
+
throw new Oauth2Error(`Unsupported code challenge method ${options.codeChallengeMethod}`);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// src/access-token/verify-access-token-request.ts
|
|
1503
|
+
async function verifyPreAuthorizedCodeAccessTokenRequest(options) {
|
|
1504
|
+
if (options.grant.preAuthorizedCode !== options.expectedPreAuthorizedCode) {
|
|
1505
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1506
|
+
error: "invalid_grant" /* InvalidGrant */,
|
|
1507
|
+
error_description: `Invalid 'pre-authorized_code' provided`
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
if (options.grant.txCode !== options.expectedTxCode) {
|
|
1511
|
+
if (!options.expectedTxCode) {
|
|
1512
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1513
|
+
error: "invalid_request" /* InvalidRequest */,
|
|
1514
|
+
error_description: `Request contains 'tx_code' that was not expected`
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
if (!options.grant.txCode) {
|
|
1518
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1519
|
+
error: "invalid_request" /* InvalidRequest */,
|
|
1520
|
+
error_description: `Missing required 'tx_code' in request`
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1524
|
+
error: "invalid_grant" /* InvalidGrant */,
|
|
1525
|
+
error_description: `Invalid 'tx_code' provided`
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
if (options.preAuthorizedCodeExpiresAt) {
|
|
1529
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
1530
|
+
if (now.getTime() > options.preAuthorizedCodeExpiresAt.getTime()) {
|
|
1531
|
+
throw new Oauth2ServerErrorResponseError(
|
|
1532
|
+
{
|
|
1533
|
+
error: "invalid_grant" /* InvalidGrant */,
|
|
1534
|
+
error_description: `Expired 'pre-authorized_code' provided`
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
internalMessage: `The provided 'pre-authorized_code' in the request expired at '${options.preAuthorizedCodeExpiresAt.getTime()}', now is '${now.getTime()}'`
|
|
1538
|
+
}
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (options.pkce) {
|
|
1543
|
+
await verifyAccessTokenRequestPkce(options.pkce, options.callbacks);
|
|
1544
|
+
}
|
|
1545
|
+
const dpopResult = options.dpop ? await verifyAccessTokenRequestDpop(options.dpop, options.request, options.callbacks) : null;
|
|
1546
|
+
return { dpopJwk: dpopResult?.dpopJwk };
|
|
1547
|
+
}
|
|
1548
|
+
async function verifyAuthorizationCodeAccessTokenRequest(options) {
|
|
1549
|
+
if (options.grant.code !== options.expectedCode) {
|
|
1550
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1551
|
+
error: "invalid_grant" /* InvalidGrant */,
|
|
1552
|
+
error_description: `Invalid 'code' provided`
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
if (options.codeExpiresAt) {
|
|
1556
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
1557
|
+
if (now.getTime() > options.codeExpiresAt.getTime()) {
|
|
1558
|
+
throw new Oauth2ServerErrorResponseError(
|
|
1559
|
+
{
|
|
1560
|
+
error: "invalid_grant" /* InvalidGrant */,
|
|
1561
|
+
error_description: `Expired 'code' provided`
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
internalMessage: `The provided 'code' in the request expired at '${options.codeExpiresAt.getTime()}', now is '${now.getTime()}'`
|
|
1565
|
+
}
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (options.pkce) {
|
|
1570
|
+
await verifyAccessTokenRequestPkce(options.pkce, options.callbacks);
|
|
1571
|
+
}
|
|
1572
|
+
const dpopResult = options.dpop ? await verifyAccessTokenRequestDpop(options.dpop, options.request, options.callbacks) : null;
|
|
1573
|
+
return { dpopJwk: dpopResult?.dpopJwk };
|
|
1574
|
+
}
|
|
1575
|
+
async function verifyAccessTokenRequestDpop(options, request, callbacks) {
|
|
1576
|
+
if (options.required && !options.jwt) {
|
|
1577
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1578
|
+
error: "invalid_dpop_proof" /* InvalidDpopProof */,
|
|
1579
|
+
error_description: "Missing required DPoP proof"
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
if (!options.jwt) return null;
|
|
1583
|
+
try {
|
|
1584
|
+
const { header } = await verifyDpopJwt({
|
|
1585
|
+
callbacks,
|
|
1586
|
+
dpopJwt: options.jwt,
|
|
1587
|
+
request,
|
|
1588
|
+
allowedSigningAlgs: options.allowedSigningAlgs
|
|
1589
|
+
});
|
|
1590
|
+
return {
|
|
1591
|
+
dpopJwk: header.jwk
|
|
1592
|
+
};
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
if (error instanceof Oauth2Error) {
|
|
1595
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1596
|
+
error: "invalid_dpop_proof" /* InvalidDpopProof */,
|
|
1597
|
+
error_description: error.message
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
throw error;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
async function verifyAccessTokenRequestPkce(options, callbacks) {
|
|
1604
|
+
if (options.codeChallenge && !options.codeVerifier) {
|
|
1605
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1606
|
+
error: "invalid_request" /* InvalidRequest */,
|
|
1607
|
+
error_description: `Missing required 'code_verifier' in access token request`
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
if (!options.codeVerifier) return null;
|
|
1611
|
+
try {
|
|
1612
|
+
await verifyPkce({
|
|
1613
|
+
callbacks,
|
|
1614
|
+
codeChallenge: options.codeChallenge,
|
|
1615
|
+
codeChallengeMethod: options.codeChallengeMethod,
|
|
1616
|
+
codeVerifier: options.codeVerifier
|
|
1617
|
+
});
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
if (error instanceof Oauth2Error) {
|
|
1620
|
+
throw new Oauth2ServerErrorResponseError({
|
|
1621
|
+
error: "invalid_grant" /* InvalidGrant */,
|
|
1622
|
+
error_description: error.message
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
throw error;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// src/authorization-challenge/create-authorization-challenge-response.ts
|
|
1630
|
+
import { parseWithErrorHandling as parseWithErrorHandling7 } from "@openid4vc/utils";
|
|
1631
|
+
|
|
1632
|
+
// src/authorization-challenge/v-authorization-challenge.ts
|
|
1633
|
+
import { vInteger as vInteger5 } from "@openid4vc/utils";
|
|
1634
|
+
import * as v15 from "valibot";
|
|
1635
|
+
|
|
1636
|
+
// src/authorization-request/v-authorization-request.ts
|
|
1637
|
+
import { vHttpsUrl as vHttpsUrl4 } from "@openid4vc/utils";
|
|
1638
|
+
import * as v14 from "valibot";
|
|
1639
|
+
var vAuthorizationRequest = v14.looseObject({
|
|
1640
|
+
response_type: v14.string(),
|
|
1641
|
+
client_id: v14.string(),
|
|
1642
|
+
issuer_state: v14.optional(v14.string()),
|
|
1643
|
+
redirect_uri: v14.optional(v14.pipe(v14.string(), v14.url())),
|
|
1644
|
+
resource: v14.optional(vHttpsUrl4),
|
|
1645
|
+
scope: v14.optional(v14.string()),
|
|
1646
|
+
// DPoP jwk thumbprint
|
|
1647
|
+
dpop_jkt: v14.optional(v14.string()),
|
|
1648
|
+
code_challenge: v14.optional(v14.string()),
|
|
1649
|
+
code_challenge_method: v14.optional(v14.string())
|
|
1650
|
+
});
|
|
1651
|
+
var vPushedAuthorizationRequest = v14.looseObject({
|
|
1652
|
+
request_uri: v14.string(),
|
|
1653
|
+
client_id: v14.string()
|
|
1654
|
+
});
|
|
1655
|
+
var vPushedAuthorizationResponse = v14.looseObject({
|
|
1656
|
+
request_uri: v14.string(),
|
|
1657
|
+
expires_in: v14.pipe(v14.number(), v14.integer())
|
|
1658
|
+
});
|
|
1659
|
+
|
|
1660
|
+
// src/authorization-challenge/v-authorization-challenge.ts
|
|
1661
|
+
var vAuthorizationChallengeRequest = v15.looseObject({
|
|
1662
|
+
// authorization challenge request can include same parameters as an authorization request
|
|
1663
|
+
// except for response_type (always `code`), and `client_id` is optional (becase
|
|
1664
|
+
// it's possible to do client authentication using different methods)
|
|
1665
|
+
...v15.omit(vAuthorizationRequest, ["response_type", "client_id"]).entries,
|
|
1666
|
+
client_id: v15.optional(vAuthorizationRequest.entries.client_id),
|
|
1667
|
+
auth_session: v15.optional(v15.string()),
|
|
1668
|
+
// DRAFT presentation during issuance
|
|
1669
|
+
presentation_during_issuance_session: v15.optional(v15.string())
|
|
1670
|
+
});
|
|
1671
|
+
var vAuthorizationChallengeResponse = v15.looseObject({
|
|
1672
|
+
authorization_code: v15.string()
|
|
1673
|
+
});
|
|
1674
|
+
var vAuthorizationChallengeErrorResponse = v15.looseObject({
|
|
1675
|
+
...vOauth2ErrorResponse.entries,
|
|
1676
|
+
auth_session: v15.optional(v15.string()),
|
|
1677
|
+
request_uri: v15.optional(v15.string()),
|
|
1678
|
+
expires_in: v15.optional(vInteger5),
|
|
1679
|
+
// DRAFT: presentation during issuance
|
|
1680
|
+
presentation: v15.optional(v15.string())
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
// src/authorization-challenge/create-authorization-challenge-response.ts
|
|
1684
|
+
function createAuthorizationChallengeResponse(options) {
|
|
1685
|
+
const authorizationChallengeResponse = parseWithErrorHandling7(vAuthorizationChallengeResponse, {
|
|
1686
|
+
...options.additionalPayload,
|
|
1687
|
+
authorization_code: options.authorizationCode
|
|
1688
|
+
});
|
|
1689
|
+
return { authorizationChallengeResponse };
|
|
1690
|
+
}
|
|
1691
|
+
function createAuthorizationChallengeErrorResponse(options) {
|
|
1692
|
+
const authorizationChallengeErrorResponse = parseWithErrorHandling7(vAuthorizationChallengeErrorResponse, {
|
|
1693
|
+
...options.additionalPayload,
|
|
1694
|
+
// General FiPA
|
|
1695
|
+
error: options.error,
|
|
1696
|
+
error_description: options.errorDescription,
|
|
1697
|
+
auth_session: options.authSession,
|
|
1698
|
+
// Presentation during issuance
|
|
1699
|
+
presentation: options.presentation,
|
|
1700
|
+
// PAR
|
|
1701
|
+
request_uri: options.requestUri,
|
|
1702
|
+
expires_in: options.expiresIn
|
|
1703
|
+
});
|
|
1704
|
+
return authorizationChallengeErrorResponse;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// src/authorization-challenge/parse-authorization-challenge-request.ts
|
|
1708
|
+
import { parseWithErrorHandling as parseWithErrorHandling8 } from "@openid4vc/utils";
|
|
1709
|
+
function parseAuthorizationChallengeRequest(options) {
|
|
1710
|
+
const authorizationChallengeRequest = parseWithErrorHandling8(
|
|
1711
|
+
vAuthorizationChallengeRequest,
|
|
1712
|
+
options.authorizationChallengeRequest
|
|
1713
|
+
);
|
|
1714
|
+
return { authorizationChallengeRequest };
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/client-attestation/clent-attestation.ts
|
|
1718
|
+
import { dateToSeconds as dateToSeconds4, parseWithErrorHandling as parseWithErrorHandling9 } from "@openid4vc/utils";
|
|
1719
|
+
|
|
1720
|
+
// src/client-attestation/v-client-attestation.ts
|
|
1721
|
+
import * as v16 from "valibot";
|
|
1722
|
+
import { vHttpsUrl as vHttpsUrl5, vInteger as vInteger6 } from "@openid4vc/utils";
|
|
1723
|
+
var vOauthClientAttestationHeader = v16.literal("OAuth-Client-Attestation");
|
|
1724
|
+
var oauthClientAttestationHeader = vOauthClientAttestationHeader.literal;
|
|
1725
|
+
var vClientAttestationJwtPayload = v16.looseObject({
|
|
1726
|
+
...vJwtPayload.entries,
|
|
1727
|
+
iss: v16.string(),
|
|
1728
|
+
sub: v16.string(),
|
|
1729
|
+
exp: vInteger6,
|
|
1730
|
+
cnf: v16.looseObject({
|
|
1731
|
+
jwk: vJwk,
|
|
1732
|
+
key_type: v16.optional(
|
|
1733
|
+
v16.union([
|
|
1734
|
+
v16.picklist(["software", "hardware", "tee", "secure_enclave", "strong_box", "secure_element", "hsm"]),
|
|
1735
|
+
v16.string()
|
|
1736
|
+
])
|
|
1737
|
+
),
|
|
1738
|
+
user_authentication: v16.optional(
|
|
1739
|
+
v16.union([
|
|
1740
|
+
v16.picklist(["system_biometry", "system_pin", "internal_biometry", "internal_pin", "secure_element_pin"]),
|
|
1741
|
+
v16.string()
|
|
1742
|
+
])
|
|
1743
|
+
)
|
|
1744
|
+
}),
|
|
1745
|
+
aal: v16.optional(v16.string())
|
|
1746
|
+
});
|
|
1747
|
+
var vClientAttestationJwtHeader = v16.looseObject({
|
|
1748
|
+
...vJwtHeader.entries,
|
|
1749
|
+
typ: v16.literal("oauth-client-attestation+jwt")
|
|
1750
|
+
});
|
|
1751
|
+
var vOauthClientAttestationPopHeader = v16.literal("OAuth-Client-Attestation-PoP");
|
|
1752
|
+
var oauthClientAttestationPopHeader = vOauthClientAttestationPopHeader.literal;
|
|
1753
|
+
var vClientAttestationPopJwtPayload = v16.looseObject({
|
|
1754
|
+
...vJwtPayload.entries,
|
|
1755
|
+
iss: v16.string(),
|
|
1756
|
+
exp: vInteger6,
|
|
1757
|
+
aud: vHttpsUrl5,
|
|
1758
|
+
jti: v16.string(),
|
|
1759
|
+
nonce: v16.optional(v16.string())
|
|
1760
|
+
});
|
|
1761
|
+
var vClientAttestationPopJwtHeader = v16.looseObject({
|
|
1762
|
+
...vJwtHeader.entries,
|
|
1763
|
+
typ: v16.literal("oauth-client-attestation-pop+jwt")
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
// src/client-attestation/clent-attestation.ts
|
|
1767
|
+
async function verifyClientAttestationJwt(options) {
|
|
1768
|
+
const { header, payload } = decodeJwt({
|
|
1769
|
+
jwt: options.clientAttestationJwt,
|
|
1770
|
+
headerSchema: vClientAttestationJwtHeader,
|
|
1771
|
+
payloadSchema: vClientAttestationJwtPayload
|
|
1772
|
+
});
|
|
1773
|
+
const { signer } = await verifyJwt({
|
|
1774
|
+
signer: jwtSignerFromJwt({ header, payload }),
|
|
1775
|
+
now: options.now,
|
|
1776
|
+
header,
|
|
1777
|
+
payload,
|
|
1778
|
+
compact: options.clientAttestationJwt,
|
|
1779
|
+
verifyJwtCallback: options.callbacks.verifyJwt,
|
|
1780
|
+
errorMessage: "client attestation jwt verification failed"
|
|
1781
|
+
});
|
|
1782
|
+
return {
|
|
1783
|
+
header,
|
|
1784
|
+
payload,
|
|
1785
|
+
signer
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
async function createClientAttestationJwt(options) {
|
|
1789
|
+
const header = parseWithErrorHandling9(vClientAttestationJwtHeader, {
|
|
1790
|
+
typ: "oauth-client-attestation+jwt",
|
|
1791
|
+
...jwtHeaderFromJwtSigner(options.signer)
|
|
1792
|
+
});
|
|
1793
|
+
const payload = parseWithErrorHandling9(vClientAttestationJwtPayload, {
|
|
1794
|
+
iss: options.issuer,
|
|
1795
|
+
iat: dateToSeconds4(options.issuedAt),
|
|
1796
|
+
exp: dateToSeconds4(options.expiresAt),
|
|
1797
|
+
sub: options.clientId,
|
|
1798
|
+
cnf: options.confirmation,
|
|
1799
|
+
...options.additionalPayload
|
|
1800
|
+
});
|
|
1801
|
+
const { jwt } = await options.callbacks.signJwt(options.signer, {
|
|
1802
|
+
header,
|
|
1803
|
+
payload
|
|
1804
|
+
});
|
|
1805
|
+
return jwt;
|
|
1806
|
+
}
|
|
1807
|
+
function extractClientAttestationJwtsFromHeaders(headers) {
|
|
1808
|
+
const clientAttestationHeader = headers.get(oauthClientAttestationHeader);
|
|
1809
|
+
const clientAttestationPopHeader = headers.get(oauthClientAttestationPopHeader);
|
|
1810
|
+
if (!clientAttestationHeader || clientAttestationHeader.includes(",")) {
|
|
1811
|
+
throw new Oauth2Error(`Missing or invalid '${oauthClientAttestationHeader}' header.`);
|
|
1812
|
+
}
|
|
1813
|
+
if (!clientAttestationPopHeader || clientAttestationPopHeader.includes(",")) {
|
|
1814
|
+
throw new Oauth2Error(`Missing or invalid '${oauthClientAttestationPopHeader}' header.`);
|
|
1815
|
+
}
|
|
1816
|
+
return {
|
|
1817
|
+
clientAttestationPopHeader,
|
|
1818
|
+
clientAttestationHeader
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/client-attestation/client-attestation-pop.ts
|
|
1823
|
+
import { addSecondsToDate as addSecondsToDate2, dateToSeconds as dateToSeconds5, encodeToBase64Url as encodeToBase64Url6, parseWithErrorHandling as parseWithErrorHandling10 } from "@openid4vc/utils";
|
|
1824
|
+
async function createClientAttestationForRequest(options) {
|
|
1825
|
+
const clientAttestationPopJwt = await createClientAttestationPopJwt({
|
|
1826
|
+
authorizationServer: options.authorizationServer,
|
|
1827
|
+
clientAttestation: options.clientAttestation.jwt,
|
|
1828
|
+
callbacks: options.callbacks,
|
|
1829
|
+
expiresAt: options.clientAttestation.expiresAt,
|
|
1830
|
+
signer: options.clientAttestation.signer,
|
|
1831
|
+
nonce: options.clientAttestation.nonce
|
|
1832
|
+
});
|
|
1833
|
+
return {
|
|
1834
|
+
headers: {
|
|
1835
|
+
[oauthClientAttestationHeader]: options.clientAttestation.jwt,
|
|
1836
|
+
[oauthClientAttestationPopHeader]: clientAttestationPopJwt
|
|
1837
|
+
},
|
|
1838
|
+
body: options.clientAttestation.includeLegacyDraft2ClientAssertion ? {
|
|
1839
|
+
client_assertion: `${options.clientAttestation.jwt}~${clientAttestationPopJwt}`,
|
|
1840
|
+
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation"
|
|
1841
|
+
} : void 0
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
async function verifyClientAttestationPopJwt(options) {
|
|
1845
|
+
const { header, payload } = decodeJwt({
|
|
1846
|
+
jwt: options.clientAttestationPopJwt,
|
|
1847
|
+
headerSchema: vClientAttestationPopJwtHeader,
|
|
1848
|
+
payloadSchema: vClientAttestationPopJwtPayload
|
|
1849
|
+
});
|
|
1850
|
+
if (payload.iss !== options.clientAttestation.payload.sub) {
|
|
1851
|
+
throw new Oauth2Error(
|
|
1852
|
+
`Client Attestation Pop jwt contains 'iss' (client_id) value '${payload.iss}', but expected 'sub' value from client attestation '${options.clientAttestation.payload.sub}'`
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
if (payload.aud !== options.authorizationServer) {
|
|
1856
|
+
throw new Oauth2Error(
|
|
1857
|
+
`Client Attestation Pop jwt contains 'aud' value '${payload.aud}', but expected authorization server identifier '${options.authorizationServer}'`
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
const { signer } = await verifyJwt({
|
|
1861
|
+
signer: {
|
|
1862
|
+
alg: header.alg,
|
|
1863
|
+
method: "jwk",
|
|
1864
|
+
publicJwk: options.clientAttestation.payload.cnf.jwk
|
|
1865
|
+
},
|
|
1866
|
+
now: options.now,
|
|
1867
|
+
header,
|
|
1868
|
+
expectedNonce: options.expectedNonce,
|
|
1869
|
+
payload,
|
|
1870
|
+
compact: options.clientAttestationPopJwt,
|
|
1871
|
+
verifyJwtCallback: options.callbacks.verifyJwt,
|
|
1872
|
+
errorMessage: "client attestation pop jwt verification failed"
|
|
1873
|
+
});
|
|
1874
|
+
return {
|
|
1875
|
+
header,
|
|
1876
|
+
payload,
|
|
1877
|
+
signer
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
async function createClientAttestationPopJwt(options) {
|
|
1881
|
+
const header = parseWithErrorHandling10(vClientAttestationPopJwtHeader, {
|
|
1882
|
+
typ: "oauth-client-attestation-pop+jwt",
|
|
1883
|
+
alg: options.signer.alg
|
|
1884
|
+
});
|
|
1885
|
+
const clientAttestation = decodeJwt({
|
|
1886
|
+
jwt: options.clientAttestation,
|
|
1887
|
+
headerSchema: vClientAttestationJwtHeader,
|
|
1888
|
+
payloadSchema: vClientAttestationJwtPayload
|
|
1889
|
+
});
|
|
1890
|
+
const expiresAt = options.expiresAt ?? addSecondsToDate2(options.issuedAt ?? /* @__PURE__ */ new Date(), 1 * 60);
|
|
1891
|
+
const payload = parseWithErrorHandling10(vClientAttestationPopJwtPayload, {
|
|
1892
|
+
aud: options.authorizationServer,
|
|
1893
|
+
iss: clientAttestation.payload.sub,
|
|
1894
|
+
iat: dateToSeconds5(options.issuedAt),
|
|
1895
|
+
exp: dateToSeconds5(expiresAt),
|
|
1896
|
+
jti: encodeToBase64Url6(await options.callbacks.generateRandom(32)),
|
|
1897
|
+
nonce: options.nonce,
|
|
1898
|
+
...options.additionalPayload
|
|
1899
|
+
});
|
|
1900
|
+
const { jwt } = await options.callbacks.signJwt(options.signer, {
|
|
1901
|
+
header,
|
|
1902
|
+
payload
|
|
1903
|
+
});
|
|
1904
|
+
return jwt;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// src/Oauth2AuthorizationServer.ts
|
|
1908
|
+
var Oauth2AuthorizationServer = class {
|
|
1909
|
+
constructor(options) {
|
|
1910
|
+
this.options = options;
|
|
1911
|
+
}
|
|
1912
|
+
createAuthorizationServerMetadata(authorizationServerMetadata) {
|
|
1913
|
+
return parseWithErrorHandling11(
|
|
1914
|
+
vAuthorizationServerMetadata,
|
|
1915
|
+
authorizationServerMetadata,
|
|
1916
|
+
"Error validating authorization server metadata"
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Parse access token request and extract the grant specific properties.
|
|
1921
|
+
*
|
|
1922
|
+
* If something goes wrong, such as the grant is not supported, missing parameters, etc,
|
|
1923
|
+
* it will throw `Oauth2ServerErrorResponseError` containing an error response object
|
|
1924
|
+
* that can be returned to the client.
|
|
1925
|
+
*/
|
|
1926
|
+
parseAccessTokenRequest(options) {
|
|
1927
|
+
return parseAccessTokenRequest(options);
|
|
1928
|
+
}
|
|
1929
|
+
verifyPreAuthorizedCodeAccessTokenRequest(options) {
|
|
1930
|
+
return verifyPreAuthorizedCodeAccessTokenRequest({
|
|
1931
|
+
...options,
|
|
1932
|
+
callbacks: this.options.callbacks
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
verifyAuthorizationCodeAccessTokenRequest(options) {
|
|
1936
|
+
return verifyAuthorizationCodeAccessTokenRequest({
|
|
1937
|
+
...options,
|
|
1938
|
+
callbacks: this.options.callbacks
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Create an access token response.
|
|
1943
|
+
*
|
|
1944
|
+
* The `sub` claim can be used to identify the resource owner is subsequent requests.
|
|
1945
|
+
* For pre-auth flow this can be the pre-authorized_code but there are no requirements
|
|
1946
|
+
* on the value.
|
|
1947
|
+
*/
|
|
1948
|
+
async createAccessTokenResponse(options) {
|
|
1949
|
+
const { jwt: accessToken } = await createAccessTokenJwt({
|
|
1950
|
+
audience: options.audience,
|
|
1951
|
+
authorizationServer: options.authorizationServer,
|
|
1952
|
+
callbacks: this.options.callbacks,
|
|
1953
|
+
expiresInSeconds: options.expiresInSeconds,
|
|
1954
|
+
subject: options.subject,
|
|
1955
|
+
scope: options.scope,
|
|
1956
|
+
clientId: options.clientId,
|
|
1957
|
+
signer: options.signer,
|
|
1958
|
+
dpopJwk: options.dpopJwk,
|
|
1959
|
+
now: options.now,
|
|
1960
|
+
additionalPayload: options.additionalAccessTokenPayload
|
|
1961
|
+
});
|
|
1962
|
+
return createAccessTokenResponse({
|
|
1963
|
+
accessToken,
|
|
1964
|
+
callbacks: this.options.callbacks,
|
|
1965
|
+
expiresInSeconds: options.expiresInSeconds,
|
|
1966
|
+
tokenType: options.dpopJwk ? "DPoP" : "Bearer",
|
|
1967
|
+
cNonce: options.cNonce,
|
|
1968
|
+
cNonceExpiresIn: options.cNonceExpiresIn,
|
|
1969
|
+
additionalPayload: options.additionalAccessTokenResponsePayload
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Parse an authorization challenge request
|
|
1974
|
+
*/
|
|
1975
|
+
parseAuthorizationChallengeRequest(options) {
|
|
1976
|
+
return parseAuthorizationChallengeRequest(options);
|
|
1977
|
+
}
|
|
1978
|
+
createAuthorizationChallengeResponse(options) {
|
|
1979
|
+
return createAuthorizationChallengeResponse(options);
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Create an authorization challenge error response indicating presentation of credenitals
|
|
1983
|
+
* using OpenID4VP is required before authorization can be granted.
|
|
1984
|
+
*
|
|
1985
|
+
* The `presentation` parameter should be an OpenID4VP authorization request url.
|
|
1986
|
+
* The `authSession` should be used to track the session
|
|
1987
|
+
*/
|
|
1988
|
+
createAuthorizationChallengePresentationErrorResponse(options) {
|
|
1989
|
+
return createAuthorizationChallengeErrorResponse({
|
|
1990
|
+
error: "insufficient_authorization" /* InsufficientAuthorization */,
|
|
1991
|
+
errorDescription: options.errorDescription,
|
|
1992
|
+
additionalPayload: options.additionalPayload,
|
|
1993
|
+
authSession: options.authSession,
|
|
1994
|
+
presentation: options.presentation
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
createAuthorizationChallengeErrorResponse(options) {
|
|
1998
|
+
return createAuthorizationChallengeErrorResponse(options);
|
|
1999
|
+
}
|
|
2000
|
+
async verifyClientAttestation({
|
|
2001
|
+
authorizationServer,
|
|
2002
|
+
headers
|
|
2003
|
+
}) {
|
|
2004
|
+
const { clientAttestationHeader, clientAttestationPopHeader } = extractClientAttestationJwtsFromHeaders(headers);
|
|
2005
|
+
const clientAttestation = await verifyClientAttestationJwt({
|
|
2006
|
+
callbacks: this.options.callbacks,
|
|
2007
|
+
clientAttestationJwt: clientAttestationHeader
|
|
2008
|
+
});
|
|
2009
|
+
const clientAttestationPop = await verifyClientAttestationPopJwt({
|
|
2010
|
+
callbacks: this.options.callbacks,
|
|
2011
|
+
authorizationServer,
|
|
2012
|
+
clientAttestation,
|
|
2013
|
+
clientAttestationPopJwt: clientAttestationPopHeader
|
|
2014
|
+
});
|
|
2015
|
+
return {
|
|
2016
|
+
clientAttestation,
|
|
2017
|
+
clientAttestationPop
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
// src/Oauth2ResourceServer.ts
|
|
2023
|
+
var Oauth2ResourceServer = class {
|
|
2024
|
+
constructor(options) {
|
|
2025
|
+
this.options = options;
|
|
2026
|
+
}
|
|
2027
|
+
async verifyResourceRequest(options) {
|
|
2028
|
+
return verifyResourceRequest({
|
|
2029
|
+
callbacks: this.options.callbacks,
|
|
2030
|
+
...options
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
// src/Oauth2Client.ts
|
|
2036
|
+
import { objectToQueryParams as objectToQueryParams5 } from "@openid4vc/utils";
|
|
2037
|
+
|
|
2038
|
+
// src/access-token/retrieve-access-token.ts
|
|
2039
|
+
import { ContentType as ContentType4, createValibotFetcher as createValibotFetcher4, objectToQueryParams as objectToQueryParams2, parseWithErrorHandling as parseWithErrorHandling12 } from "@openid4vc/utils";
|
|
2040
|
+
import { InvalidFetchResponseError as InvalidFetchResponseError4 } from "@openid4vc/utils";
|
|
2041
|
+
import * as v17 from "valibot";
|
|
2042
|
+
async function retrievePreAuthorizedCodeAccessToken(options) {
|
|
2043
|
+
const request = {
|
|
2044
|
+
grant_type: preAuthorizedCodeGrantIdentifier,
|
|
2045
|
+
"pre-authorized_code": options.preAuthorizedCode,
|
|
2046
|
+
tx_code: options.txCode,
|
|
2047
|
+
resource: options.resource,
|
|
2048
|
+
...options.additionalRequestPayload
|
|
2049
|
+
};
|
|
2050
|
+
return retrieveAccessToken({
|
|
2051
|
+
authorizationServerMetadata: options.authorizationServerMetadata,
|
|
2052
|
+
request,
|
|
2053
|
+
dpop: options.dpop,
|
|
2054
|
+
callbacks: options.callbacks,
|
|
2055
|
+
resource: options.resource,
|
|
2056
|
+
clientAttestation: options.clientAttestation
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
async function retrieveAuthorizationCodeAccessToken(options) {
|
|
2060
|
+
const request = {
|
|
2061
|
+
grant_type: authorizationCodeGrantIdentifier,
|
|
2062
|
+
code: options.authorizationCode,
|
|
2063
|
+
code_verifier: options.pkceCodeVerifier,
|
|
2064
|
+
redirect_uri: options.redirectUri,
|
|
2065
|
+
resource: options.resource,
|
|
2066
|
+
...options.additionalRequestPayload
|
|
2067
|
+
};
|
|
2068
|
+
return retrieveAccessToken({
|
|
2069
|
+
authorizationServerMetadata: options.authorizationServerMetadata,
|
|
2070
|
+
request,
|
|
2071
|
+
dpop: options.dpop,
|
|
2072
|
+
resource: options.resource,
|
|
2073
|
+
callbacks: options.callbacks,
|
|
2074
|
+
clientAttestation: options.clientAttestation
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
async function retrieveRefreshTokenAccessToken(options) {
|
|
2078
|
+
const request = {
|
|
2079
|
+
grant_type: refreshTokenGrantIdentifier,
|
|
2080
|
+
refresh_token: options.refreshToken,
|
|
2081
|
+
resource: options.resource,
|
|
2082
|
+
...options.additionalRequestPayload
|
|
2083
|
+
};
|
|
2084
|
+
return retrieveAccessToken({
|
|
2085
|
+
authorizationServerMetadata: options.authorizationServerMetadata,
|
|
2086
|
+
request,
|
|
2087
|
+
dpop: options.dpop,
|
|
2088
|
+
callbacks: options.callbacks,
|
|
2089
|
+
resource: options.resource,
|
|
2090
|
+
clientAttestation: options.clientAttestation
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
async function retrieveAccessToken(options) {
|
|
2094
|
+
const fetchWithValibot = createValibotFetcher4(options.callbacks.fetch);
|
|
2095
|
+
const accessTokenRequest = parseWithErrorHandling12(
|
|
2096
|
+
vAccessTokenRequest,
|
|
2097
|
+
options.request,
|
|
2098
|
+
"Error validating access token request"
|
|
2099
|
+
);
|
|
2100
|
+
if (accessTokenRequest.tx_code) {
|
|
2101
|
+
accessTokenRequest.user_pin = accessTokenRequest.tx_code;
|
|
2102
|
+
}
|
|
2103
|
+
const clientAttestation = options.clientAttestation ? await createClientAttestationForRequest({
|
|
2104
|
+
authorizationServer: options.authorizationServerMetadata.issuer,
|
|
2105
|
+
clientAttestation: options.clientAttestation,
|
|
2106
|
+
callbacks: options.callbacks
|
|
2107
|
+
}) : void 0;
|
|
2108
|
+
return await authorizationServerRequestWithDpopRetry({
|
|
2109
|
+
dpop: options.dpop,
|
|
2110
|
+
request: async (dpop) => {
|
|
2111
|
+
const dpopHeaders = dpop ? await createDpopHeadersForRequest({
|
|
2112
|
+
request: {
|
|
2113
|
+
method: "POST",
|
|
2114
|
+
url: options.authorizationServerMetadata.token_endpoint
|
|
2115
|
+
},
|
|
2116
|
+
signer: dpop.signer,
|
|
2117
|
+
callbacks: options.callbacks,
|
|
2118
|
+
nonce: dpop.nonce
|
|
2119
|
+
}) : void 0;
|
|
2120
|
+
const requestQueryParams = objectToQueryParams2({
|
|
2121
|
+
...accessTokenRequest,
|
|
2122
|
+
...clientAttestation?.body
|
|
2123
|
+
});
|
|
2124
|
+
const { response, result } = await fetchWithValibot(
|
|
2125
|
+
vAccessTokenResponse,
|
|
2126
|
+
ContentType4.Json,
|
|
2127
|
+
options.authorizationServerMetadata.token_endpoint,
|
|
2128
|
+
{
|
|
2129
|
+
body: requestQueryParams.toString(),
|
|
2130
|
+
method: "POST",
|
|
2131
|
+
headers: {
|
|
2132
|
+
"Content-Type": ContentType4.XWwwFormUrlencoded,
|
|
2133
|
+
...clientAttestation?.headers,
|
|
2134
|
+
...dpopHeaders
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
);
|
|
2138
|
+
if (!response.ok || !result) {
|
|
2139
|
+
const tokenErrorResponse = v17.safeParse(
|
|
2140
|
+
vAccessTokenErrorResponse,
|
|
2141
|
+
await response.clone().json().catch(() => null)
|
|
2142
|
+
);
|
|
2143
|
+
if (tokenErrorResponse.success) {
|
|
2144
|
+
throw new Oauth2ClientErrorResponseError(
|
|
2145
|
+
`Unable to retrieve access token from '${options.authorizationServerMetadata.token_endpoint}'. Received token error response with status ${response.status}`,
|
|
2146
|
+
tokenErrorResponse.output,
|
|
2147
|
+
response
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
throw new InvalidFetchResponseError4(
|
|
2151
|
+
`Unable to retrieve access token from '${options.authorizationServerMetadata.token_endpoint}'. Received response with status ${response.status}`,
|
|
2152
|
+
await response.clone().text(),
|
|
2153
|
+
response
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
if (!result.success) {
|
|
2157
|
+
throw new ValidationError("Error validating access token response", result.issues);
|
|
2158
|
+
}
|
|
2159
|
+
const dpopNonce = extractDpopNonceFromHeaders(response.headers) ?? void 0;
|
|
2160
|
+
return {
|
|
2161
|
+
dpop: dpop ? {
|
|
2162
|
+
...dpop,
|
|
2163
|
+
nonce: dpopNonce
|
|
2164
|
+
} : void 0,
|
|
2165
|
+
accessTokenResponse: result.output
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// src/authorization-challenge/send-authorization-challenge.ts
|
|
2172
|
+
import {
|
|
2173
|
+
ContentType as ContentType5,
|
|
2174
|
+
ValidationError as ValidationError3,
|
|
2175
|
+
createValibotFetcher as createValibotFetcher5,
|
|
2176
|
+
objectToQueryParams as objectToQueryParams3,
|
|
2177
|
+
parseWithErrorHandling as parseWithErrorHandling13
|
|
2178
|
+
} from "@openid4vc/utils";
|
|
2179
|
+
import { InvalidFetchResponseError as InvalidFetchResponseError5 } from "@openid4vc/utils";
|
|
2180
|
+
import * as v18 from "valibot";
|
|
2181
|
+
async function sendAuthorizationChallengeRequest(options) {
|
|
2182
|
+
const fetchWithValibot = createValibotFetcher5(options.callbacks.fetch);
|
|
2183
|
+
const authorizationServerMetadata = options.authorizationServerMetadata;
|
|
2184
|
+
const authorizationChallengeEndpoint = authorizationServerMetadata.authorization_challenge_endpoint;
|
|
2185
|
+
if (!authorizationChallengeEndpoint) {
|
|
2186
|
+
throw new Oauth2Error(
|
|
2187
|
+
`Unable to send authorization challange. Authorization server '${authorizationServerMetadata.issuer}' has no 'authorization_challenge_endpoint'`
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
const pkce = authorizationServerMetadata.code_challenge_methods_supported && !options.authSession ? await createPkce({
|
|
2191
|
+
allowedCodeChallengeMethods: authorizationServerMetadata.code_challenge_methods_supported,
|
|
2192
|
+
callbacks: options.callbacks,
|
|
2193
|
+
codeVerifier: options.pkceCodeVerifier
|
|
2194
|
+
}) : void 0;
|
|
2195
|
+
const clientAttestation = options.clientAttestation ? await createClientAttestationForRequest({
|
|
2196
|
+
authorizationServer: options.authorizationServerMetadata.issuer,
|
|
2197
|
+
clientAttestation: options.clientAttestation,
|
|
2198
|
+
callbacks: options.callbacks
|
|
2199
|
+
}) : void 0;
|
|
2200
|
+
const authorizationChallengeRequest = parseWithErrorHandling13(vAuthorizationChallengeRequest, {
|
|
2201
|
+
...options.additionalRequestPayload,
|
|
2202
|
+
auth_session: options.authSession,
|
|
2203
|
+
client_id: options.clientId,
|
|
2204
|
+
scope: options.scope,
|
|
2205
|
+
resource: options.resource,
|
|
2206
|
+
code_challenge: pkce?.codeChallenge,
|
|
2207
|
+
code_challenge_method: pkce?.codeChallengeMethod,
|
|
2208
|
+
presentation_during_issuance_session: options.presentationDuringIssuanceSession,
|
|
2209
|
+
...clientAttestation?.body
|
|
2210
|
+
});
|
|
2211
|
+
return authorizationServerRequestWithDpopRetry({
|
|
2212
|
+
dpop: options.dpop,
|
|
2213
|
+
request: async (dpop) => {
|
|
2214
|
+
const dpopHeaders = dpop ? await createDpopHeadersForRequest({
|
|
2215
|
+
request: {
|
|
2216
|
+
method: "POST",
|
|
2217
|
+
url: authorizationChallengeEndpoint
|
|
2218
|
+
},
|
|
2219
|
+
signer: dpop.signer,
|
|
2220
|
+
callbacks: options.callbacks,
|
|
2221
|
+
nonce: dpop.nonce
|
|
2222
|
+
}) : void 0;
|
|
2223
|
+
const { response, result } = await fetchWithValibot(
|
|
2224
|
+
vAuthorizationChallengeResponse,
|
|
2225
|
+
ContentType5.Json,
|
|
2226
|
+
authorizationChallengeEndpoint,
|
|
2227
|
+
{
|
|
2228
|
+
method: "POST",
|
|
2229
|
+
body: objectToQueryParams3(authorizationChallengeRequest).toString(),
|
|
2230
|
+
headers: {
|
|
2231
|
+
...clientAttestation?.headers,
|
|
2232
|
+
...dpopHeaders,
|
|
2233
|
+
"Content-Type": ContentType5.XWwwFormUrlencoded
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
);
|
|
2237
|
+
if (!response.ok || !result) {
|
|
2238
|
+
const authorizationChallengeErrorResponse = v18.safeParse(
|
|
2239
|
+
vAuthorizationChallengeErrorResponse,
|
|
2240
|
+
await response.clone().json().catch(() => null)
|
|
2241
|
+
);
|
|
2242
|
+
if (authorizationChallengeErrorResponse.success) {
|
|
2243
|
+
throw new Oauth2ClientAuthorizationChallengeError(
|
|
2244
|
+
`Error requesting authorization code from authorization challenge endpoint '${authorizationServerMetadata.authorization_challenge_endpoint}'. Received response with status ${response.status}`,
|
|
2245
|
+
authorizationChallengeErrorResponse.output,
|
|
2246
|
+
response
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
throw new InvalidFetchResponseError5(
|
|
2250
|
+
`Error requesting authorization code from authorization challenge endpoint '${authorizationServerMetadata.authorization_challenge_endpoint}'. Received response with status ${response.status}`,
|
|
2251
|
+
await response.clone().text(),
|
|
2252
|
+
response
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
if (!result.success) {
|
|
2256
|
+
throw new ValidationError3("Error validating authorization challenge response", result.issues);
|
|
2257
|
+
}
|
|
2258
|
+
const dpopNonce = extractDpopNonceFromHeaders(response.headers) ?? void 0;
|
|
2259
|
+
return {
|
|
2260
|
+
pkce,
|
|
2261
|
+
dpop: dpop ? {
|
|
2262
|
+
...dpop,
|
|
2263
|
+
nonce: dpopNonce
|
|
2264
|
+
} : void 0,
|
|
2265
|
+
authorizationChallengeResponse: result.output
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/authorization-request/create-authorization-request.ts
|
|
2272
|
+
import { ContentType as ContentType6, createValibotFetcher as createValibotFetcher6, objectToQueryParams as objectToQueryParams4 } from "@openid4vc/utils";
|
|
2273
|
+
import { InvalidFetchResponseError as InvalidFetchResponseError6 } from "@openid4vc/utils";
|
|
2274
|
+
import * as v19 from "valibot";
|
|
2275
|
+
async function createAuthorizationRequestUrl(options) {
|
|
2276
|
+
const authorizationServerMetadata = options.authorizationServerMetadata;
|
|
2277
|
+
const pushedAuthorizationRequestEndpoint = authorizationServerMetadata.pushed_authorization_request_endpoint;
|
|
2278
|
+
if (!authorizationServerMetadata.authorization_endpoint) {
|
|
2279
|
+
throw new Oauth2Error(
|
|
2280
|
+
`Unable to create authorization request url. Authorization server '${authorizationServerMetadata.issuer}' has no 'authorization_endpoint'`
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
const pkce = authorizationServerMetadata.code_challenge_methods_supported ? await createPkce({
|
|
2284
|
+
allowedCodeChallengeMethods: authorizationServerMetadata.code_challenge_methods_supported,
|
|
2285
|
+
callbacks: options.callbacks,
|
|
2286
|
+
codeVerifier: options.pkceCodeVerifier
|
|
2287
|
+
}) : void 0;
|
|
2288
|
+
const authorizationRequest = {
|
|
2289
|
+
...options.additionalRequestPayload,
|
|
2290
|
+
response_type: "code",
|
|
2291
|
+
client_id: options.clientId,
|
|
2292
|
+
redirect_uri: options.redirectUri,
|
|
2293
|
+
resource: options.resource,
|
|
2294
|
+
scope: options.scope,
|
|
2295
|
+
code_challenge: pkce?.codeChallenge,
|
|
2296
|
+
code_challenge_method: pkce?.codeChallengeMethod
|
|
2297
|
+
};
|
|
2298
|
+
let pushedAuthorizationRequest = void 0;
|
|
2299
|
+
let dpop = options.dpop;
|
|
2300
|
+
if (authorizationServerMetadata.require_pushed_authorization_requests || pushedAuthorizationRequestEndpoint) {
|
|
2301
|
+
if (!pushedAuthorizationRequestEndpoint) {
|
|
2302
|
+
throw new Oauth2Error(
|
|
2303
|
+
`Authorization server '${authorizationServerMetadata.issuer}' indicated that pushed authorization requests are required, but the 'pushed_authorization_request_endpoint' is missing in the authorization server metadata.`
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
const clientAttestation = options.clientAttestation ? await createClientAttestationForRequest({
|
|
2307
|
+
authorizationServer: options.authorizationServerMetadata.issuer,
|
|
2308
|
+
clientAttestation: options.clientAttestation,
|
|
2309
|
+
callbacks: options.callbacks
|
|
2310
|
+
}) : void 0;
|
|
2311
|
+
const { pushedAuthorizationResponse, dpopNonce } = await authorizationServerRequestWithDpopRetry({
|
|
2312
|
+
dpop: options.dpop,
|
|
2313
|
+
request: async (dpop2) => {
|
|
2314
|
+
const dpopHeaders = dpop2 ? await createDpopHeadersForRequest({
|
|
2315
|
+
request: {
|
|
2316
|
+
method: "POST",
|
|
2317
|
+
url: pushedAuthorizationRequestEndpoint
|
|
2318
|
+
},
|
|
2319
|
+
signer: dpop2.signer,
|
|
2320
|
+
callbacks: options.callbacks,
|
|
2321
|
+
nonce: dpop2.nonce
|
|
2322
|
+
}) : void 0;
|
|
2323
|
+
return await pushAuthorizationRequest({
|
|
2324
|
+
authorizationRequest: {
|
|
2325
|
+
...authorizationRequest,
|
|
2326
|
+
...clientAttestation?.headers
|
|
2327
|
+
},
|
|
2328
|
+
pushedAuthorizationRequestEndpoint,
|
|
2329
|
+
fetch: options.callbacks.fetch,
|
|
2330
|
+
headers: {
|
|
2331
|
+
...clientAttestation?.headers,
|
|
2332
|
+
...dpopHeaders
|
|
2333
|
+
}
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
pushedAuthorizationRequest = {
|
|
2338
|
+
request_uri: pushedAuthorizationResponse.request_uri,
|
|
2339
|
+
client_id: authorizationRequest.client_id
|
|
2340
|
+
};
|
|
2341
|
+
if (options.dpop && dpopNonce) {
|
|
2342
|
+
dpop = {
|
|
2343
|
+
...options.dpop,
|
|
2344
|
+
nonce: dpopNonce
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
} else {
|
|
2348
|
+
if (options.dpop) {
|
|
2349
|
+
authorizationRequest.dpop_jkt = await calculateJwkThumbprint({
|
|
2350
|
+
hashAlgorithm: "SHA-256" /* Sha256 */,
|
|
2351
|
+
hashCallback: options.callbacks.hash,
|
|
2352
|
+
jwk: options.dpop.signer.publicJwk
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
const authorizationRequestUrl = `${authorizationServerMetadata.authorization_endpoint}?${objectToQueryParams4(pushedAuthorizationRequest ?? authorizationRequest).toString()}`;
|
|
2357
|
+
return {
|
|
2358
|
+
authorizationRequestUrl,
|
|
2359
|
+
pkce,
|
|
2360
|
+
dpop
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
async function pushAuthorizationRequest(options) {
|
|
2364
|
+
const fetchWithValibot = createValibotFetcher6(options.fetch);
|
|
2365
|
+
if (options.authorizationRequest.request_uri) {
|
|
2366
|
+
throw new Oauth2Error(
|
|
2367
|
+
`Authorization request contains 'request_uri' parameter. This is not allowed for pushed authorization reuqests.`
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
const { response, result } = await fetchWithValibot(
|
|
2371
|
+
vPushedAuthorizationResponse,
|
|
2372
|
+
ContentType6.Json,
|
|
2373
|
+
options.pushedAuthorizationRequestEndpoint,
|
|
2374
|
+
{
|
|
2375
|
+
method: "POST",
|
|
2376
|
+
body: objectToQueryParams4(options.authorizationRequest).toString(),
|
|
2377
|
+
headers: {
|
|
2378
|
+
...options.headers,
|
|
2379
|
+
"Content-Type": ContentType6.XWwwFormUrlencoded
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
);
|
|
2383
|
+
if (!response.ok || !result) {
|
|
2384
|
+
const parErrorResponse = v19.safeParse(
|
|
2385
|
+
vOauth2ErrorResponse,
|
|
2386
|
+
await response.clone().json().catch(() => null)
|
|
2387
|
+
);
|
|
2388
|
+
if (parErrorResponse.success) {
|
|
2389
|
+
throw new Oauth2ClientErrorResponseError(
|
|
2390
|
+
`Unable to push authorization request to '${options.pushedAuthorizationRequestEndpoint}'. Received response with status ${response.status}`,
|
|
2391
|
+
parErrorResponse.output,
|
|
2392
|
+
response
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
throw new InvalidFetchResponseError6(
|
|
2396
|
+
`Unable to push authorization request to '${options.pushedAuthorizationRequestEndpoint}'. Received response with status ${response.status}`,
|
|
2397
|
+
await response.clone().text(),
|
|
2398
|
+
response
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
if (!result.success) {
|
|
2402
|
+
throw new ValidationError("Error validating pushed authorization response", result.issues);
|
|
2403
|
+
}
|
|
2404
|
+
const dpopNonce = extractDpopNonceFromHeaders(response.headers);
|
|
2405
|
+
return {
|
|
2406
|
+
dpopNonce,
|
|
2407
|
+
pushedAuthorizationResponse: result.output
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// src/Oauth2Client.ts
|
|
2412
|
+
var Oauth2Client = class {
|
|
2413
|
+
constructor(options) {
|
|
2414
|
+
this.options = options;
|
|
2415
|
+
}
|
|
2416
|
+
isDpopSupported(options) {
|
|
2417
|
+
if (!options.authorizationServerMetadata.dpop_signing_alg_values_supported || options.authorizationServerMetadata.dpop_signing_alg_values_supported.length === 0) {
|
|
2418
|
+
return {
|
|
2419
|
+
supported: false
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
return {
|
|
2423
|
+
supported: true,
|
|
2424
|
+
dpopSigningAlgValuesSupported: options.authorizationServerMetadata.dpop_signing_alg_values_supported
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
async fetchAuthorizationServerMetadata(issuer) {
|
|
2428
|
+
return fetchAuthorizationServerMetadata(issuer, this.options.callbacks.fetch);
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Initiate authorization.
|
|
2432
|
+
*
|
|
2433
|
+
* It will take the followings steps:
|
|
2434
|
+
* - if `authorization_challenge_endpoint` is defined, send an authorization challenge request
|
|
2435
|
+
* - if authorization challenge request returns a `redirect_to_web` error code with `request_uri`
|
|
2436
|
+
* then construct the authorization request url based on the `request_uri`
|
|
2437
|
+
* - if the `authorization_challenge_endpoint` is not defined, or authorization challenge request reuturns a `redirect_to_web` error code without `request_uri`
|
|
2438
|
+
* then the authorization request url will be constructed as usual (optionally using PAR).
|
|
2439
|
+
*
|
|
2440
|
+
* @throws {Oauth2ClientAuthorizationChallengeError} in case of an error response. If `error` is
|
|
2441
|
+
* `insufficient_authorization` possible extra steps can be taken.
|
|
2442
|
+
*/
|
|
2443
|
+
async initiateAuthorization(options) {
|
|
2444
|
+
const pkce = options.authorizationServerMetadata.code_challenge_methods_supported ? await createPkce({
|
|
2445
|
+
allowedCodeChallengeMethods: options.authorizationServerMetadata.code_challenge_methods_supported,
|
|
2446
|
+
callbacks: this.options.callbacks,
|
|
2447
|
+
codeVerifier: options.pkceCodeVerifier
|
|
2448
|
+
}) : void 0;
|
|
2449
|
+
if (options.authorizationServerMetadata.authorization_challenge_endpoint) {
|
|
2450
|
+
try {
|
|
2451
|
+
await this.sendAuthorizationChallengeRequest({
|
|
2452
|
+
authorizationServerMetadata: options.authorizationServerMetadata,
|
|
2453
|
+
additionalRequestPayload: options.additionalRequestPayload,
|
|
2454
|
+
clientId: options.clientId,
|
|
2455
|
+
pkceCodeVerifier: pkce?.codeVerifier,
|
|
2456
|
+
scope: options.scope,
|
|
2457
|
+
resource: options.resource,
|
|
2458
|
+
clientAttestation: options.clientAttestation,
|
|
2459
|
+
dpop: options.dpop
|
|
2460
|
+
});
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
const isRecoverableError = error instanceof Oauth2ClientAuthorizationChallengeError && error.errorResponse.error === "redirect_to_web" /* RedirectToWeb */;
|
|
2463
|
+
if (!isRecoverableError) throw error;
|
|
2464
|
+
if (error.errorResponse.request_uri) {
|
|
2465
|
+
const authorizationRequestUrl = `${options.authorizationServerMetadata.authorization_endpoint}?${objectToQueryParams5(
|
|
2466
|
+
{
|
|
2467
|
+
request_uri: error.errorResponse.request_uri,
|
|
2468
|
+
client_id: options.clientId
|
|
2469
|
+
}
|
|
2470
|
+
).toString()}`;
|
|
2471
|
+
const dpopNonce = extractDpopNonceFromHeaders(error.response.headers);
|
|
2472
|
+
return {
|
|
2473
|
+
dpop: options.dpop ? {
|
|
2474
|
+
...options.dpop,
|
|
2475
|
+
nonce: dpopNonce
|
|
2476
|
+
} : void 0,
|
|
2477
|
+
authorizationRequestUrl,
|
|
2478
|
+
pkce
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return this.createAuthorizationRequestUrl({
|
|
2484
|
+
authorizationServerMetadata: options.authorizationServerMetadata,
|
|
2485
|
+
clientId: options.clientId,
|
|
2486
|
+
additionalRequestPayload: options.additionalRequestPayload,
|
|
2487
|
+
redirectUri: options.redirectUri,
|
|
2488
|
+
scope: options.scope,
|
|
2489
|
+
pkceCodeVerifier: pkce?.codeVerifier,
|
|
2490
|
+
resource: options.resource,
|
|
2491
|
+
clientAttestation: options.clientAttestation,
|
|
2492
|
+
dpop: options.dpop
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
sendAuthorizationChallengeRequest(options) {
|
|
2496
|
+
return sendAuthorizationChallengeRequest({
|
|
2497
|
+
...options,
|
|
2498
|
+
callbacks: this.options.callbacks
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2501
|
+
async createAuthorizationRequestUrl(options) {
|
|
2502
|
+
return createAuthorizationRequestUrl({
|
|
2503
|
+
authorizationServerMetadata: options.authorizationServerMetadata,
|
|
2504
|
+
clientId: options.clientId,
|
|
2505
|
+
additionalRequestPayload: options.additionalRequestPayload,
|
|
2506
|
+
redirectUri: options.redirectUri,
|
|
2507
|
+
resource: options.resource,
|
|
2508
|
+
scope: options.scope,
|
|
2509
|
+
callbacks: this.options.callbacks,
|
|
2510
|
+
pkceCodeVerifier: options.pkceCodeVerifier,
|
|
2511
|
+
clientAttestation: options.clientAttestation,
|
|
2512
|
+
dpop: options.dpop
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
async retrievePreAuthorizedCodeAccessToken({
|
|
2516
|
+
authorizationServerMetadata,
|
|
2517
|
+
preAuthorizedCode,
|
|
2518
|
+
additionalRequestPayload,
|
|
2519
|
+
txCode,
|
|
2520
|
+
dpop,
|
|
2521
|
+
resource,
|
|
2522
|
+
clientAttestation
|
|
2523
|
+
}) {
|
|
2524
|
+
const result = await retrievePreAuthorizedCodeAccessToken({
|
|
2525
|
+
authorizationServerMetadata,
|
|
2526
|
+
preAuthorizedCode,
|
|
2527
|
+
txCode,
|
|
2528
|
+
resource,
|
|
2529
|
+
additionalRequestPayload: {
|
|
2530
|
+
...additionalRequestPayload,
|
|
2531
|
+
tx_code: txCode
|
|
2532
|
+
},
|
|
2533
|
+
callbacks: this.options.callbacks,
|
|
2534
|
+
dpop,
|
|
2535
|
+
clientAttestation
|
|
2536
|
+
});
|
|
2537
|
+
return result;
|
|
2538
|
+
}
|
|
2539
|
+
async retrieveAuthorizationCodeAccessToken({
|
|
2540
|
+
authorizationServerMetadata,
|
|
2541
|
+
additionalRequestPayload,
|
|
2542
|
+
authorizationCode,
|
|
2543
|
+
pkceCodeVerifier,
|
|
2544
|
+
redirectUri,
|
|
2545
|
+
resource,
|
|
2546
|
+
dpop,
|
|
2547
|
+
clientAttestation
|
|
2548
|
+
}) {
|
|
2549
|
+
const result = await retrieveAuthorizationCodeAccessToken({
|
|
2550
|
+
authorizationServerMetadata,
|
|
2551
|
+
authorizationCode,
|
|
2552
|
+
pkceCodeVerifier,
|
|
2553
|
+
additionalRequestPayload,
|
|
2554
|
+
resource,
|
|
2555
|
+
callbacks: this.options.callbacks,
|
|
2556
|
+
dpop,
|
|
2557
|
+
redirectUri,
|
|
2558
|
+
clientAttestation
|
|
2559
|
+
});
|
|
2560
|
+
return result;
|
|
2561
|
+
}
|
|
2562
|
+
async retrieveRefreshTokenAccessToken({
|
|
2563
|
+
authorizationServerMetadata,
|
|
2564
|
+
additionalRequestPayload,
|
|
2565
|
+
refreshToken,
|
|
2566
|
+
resource,
|
|
2567
|
+
dpop,
|
|
2568
|
+
clientAttestation
|
|
2569
|
+
}) {
|
|
2570
|
+
const result = await retrieveRefreshTokenAccessToken({
|
|
2571
|
+
authorizationServerMetadata,
|
|
2572
|
+
refreshToken,
|
|
2573
|
+
additionalRequestPayload,
|
|
2574
|
+
resource,
|
|
2575
|
+
callbacks: this.options.callbacks,
|
|
2576
|
+
dpop,
|
|
2577
|
+
clientAttestation
|
|
2578
|
+
});
|
|
2579
|
+
return result;
|
|
2580
|
+
}
|
|
2581
|
+
async resourceRequest(options) {
|
|
2582
|
+
return resourceRequest(options);
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* @todo move this to another class?
|
|
2586
|
+
*/
|
|
2587
|
+
async createClientAttestationJwt(options) {
|
|
2588
|
+
return await createClientAttestationJwt({
|
|
2589
|
+
callbacks: this.options.callbacks,
|
|
2590
|
+
...options
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
export {
|
|
2595
|
+
HashAlgorithm,
|
|
2596
|
+
InvalidFetchResponseError7 as InvalidFetchResponseError,
|
|
2597
|
+
Oauth2AuthorizationServer,
|
|
2598
|
+
Oauth2Client,
|
|
2599
|
+
Oauth2ClientAuthorizationChallengeError,
|
|
2600
|
+
Oauth2ClientErrorResponseError,
|
|
2601
|
+
Oauth2Error,
|
|
2602
|
+
Oauth2ErrorCodes,
|
|
2603
|
+
Oauth2JwtParseError,
|
|
2604
|
+
Oauth2JwtVerificationError,
|
|
2605
|
+
Oauth2ResourceServer,
|
|
2606
|
+
Oauth2ResourceUnauthorizedError,
|
|
2607
|
+
Oauth2ServerErrorResponseError,
|
|
2608
|
+
PkceCodeChallengeMethod,
|
|
2609
|
+
SupportedAuthenticationScheme,
|
|
2610
|
+
authorizationCodeGrantIdentifier,
|
|
2611
|
+
calculateJwkThumbprint,
|
|
2612
|
+
clientAuthenticationClientSecretBasic,
|
|
2613
|
+
clientAuthenticationClientSecretPost,
|
|
2614
|
+
clientAuthenticationDynamic,
|
|
2615
|
+
clientAuthenticationNone,
|
|
2616
|
+
decodeJwt,
|
|
2617
|
+
fetchAuthorizationServerMetadata,
|
|
2618
|
+
fetchJwks,
|
|
2619
|
+
fetchWellKnownMetadata,
|
|
2620
|
+
getAuthorizationServerMetadataFromList,
|
|
2621
|
+
getGlobalConfig,
|
|
2622
|
+
isJwkInSet,
|
|
2623
|
+
jwtHeaderFromJwtSigner,
|
|
2624
|
+
jwtSignerFromJwt,
|
|
2625
|
+
preAuthorizedCodeGrantIdentifier,
|
|
2626
|
+
refreshTokenGrantIdentifier,
|
|
2627
|
+
resourceRequest,
|
|
2628
|
+
setGlobalConfig,
|
|
2629
|
+
vAuthorizationCodeGrantIdentifier,
|
|
2630
|
+
vAuthorizationServerMetadata,
|
|
2631
|
+
vCompactJwt,
|
|
2632
|
+
vJwk,
|
|
2633
|
+
vJwtHeader,
|
|
2634
|
+
vJwtPayload,
|
|
2635
|
+
vOauth2ErrorResponse,
|
|
2636
|
+
vPreAuthorizedCodeGrantIdentifier,
|
|
2637
|
+
vRefreshTokenGrantIdentifier,
|
|
2638
|
+
verifyJwt,
|
|
2639
|
+
verifyResourceRequest
|
|
2640
|
+
};
|
|
2641
|
+
//# sourceMappingURL=index.mjs.map
|