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