oauth4webapi 2.17.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  let USER_AGENT;
2
2
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
3
3
  const NAME = 'oauth4webapi';
4
- const VERSION = 'v2.17.0';
4
+ const VERSION = 'v3.0.1';
5
5
  USER_AGENT = `${NAME}/${VERSION}`;
6
6
  }
7
7
  function looseInstanceOf(input, expected) {
@@ -16,13 +16,20 @@ function looseInstanceOf(input, expected) {
16
16
  return false;
17
17
  }
18
18
  }
19
+ const ERR_INVALID_ARG_VALUE = 'ERR_INVALID_ARG_VALUE';
20
+ const ERR_INVALID_ARG_TYPE = 'ERR_INVALID_ARG_TYPE';
21
+ function CodedTypeError(message, code, cause) {
22
+ const err = new TypeError(message, { cause });
23
+ Object.assign(err, { code });
24
+ return err;
25
+ }
26
+ export const allowInsecureRequests = Symbol();
19
27
  export const clockSkew = Symbol();
20
28
  export const clockTolerance = Symbol();
21
29
  export const customFetch = Symbol();
22
30
  export const modifyAssertion = Symbol();
23
31
  export const jweDecrypt = Symbol();
24
32
  export const jwksCache = Symbol();
25
- export const useMtlsAlias = Symbol();
26
33
  const encoder = new TextEncoder();
27
34
  const decoder = new TextDecoder();
28
35
  function buf(input) {
@@ -52,7 +59,7 @@ function decodeBase64Url(input) {
52
59
  return bytes;
53
60
  }
54
61
  catch (cause) {
55
- throw new OPE('The input to be decoded is not correctly encoded.', { cause });
62
+ throw CodedTypeError('The input to be decoded is not correctly encoded.', ERR_INVALID_ARG_VALUE, cause);
56
63
  }
57
64
  }
58
65
  function b64u(input) {
@@ -61,98 +68,45 @@ function b64u(input) {
61
68
  }
62
69
  return encodeBase64Url(input);
63
70
  }
64
- class LRU {
65
- constructor(maxSize) {
66
- this.cache = new Map();
67
- this._cache = new Map();
68
- this.maxSize = maxSize;
69
- }
70
- get(key) {
71
- let v = this.cache.get(key);
72
- if (v) {
73
- return v;
74
- }
75
- if ((v = this._cache.get(key))) {
76
- this.update(key, v);
77
- return v;
78
- }
79
- return undefined;
80
- }
81
- has(key) {
82
- return this.cache.has(key) || this._cache.has(key);
83
- }
84
- set(key, value) {
85
- if (this.cache.has(key)) {
86
- this.cache.set(key, value);
87
- }
88
- else {
89
- this.update(key, value);
90
- }
91
- return this;
92
- }
93
- delete(key) {
94
- if (this.cache.has(key)) {
95
- return this.cache.delete(key);
96
- }
97
- if (this._cache.has(key)) {
98
- return this._cache.delete(key);
99
- }
100
- return false;
101
- }
102
- update(key, value) {
103
- this.cache.set(key, value);
104
- if (this.cache.size >= this.maxSize) {
105
- this._cache = this.cache;
106
- this.cache = new Map();
107
- }
108
- }
109
- }
110
71
  export class UnsupportedOperationError extends Error {
111
- constructor(message) {
112
- super(message ?? 'operation not supported');
72
+ code;
73
+ constructor(message, options) {
74
+ super(message, options);
113
75
  this.name = this.constructor.name;
76
+ this.code = UNSUPPORTED_OPERATION;
114
77
  Error.captureStackTrace?.(this, this.constructor);
115
78
  }
116
79
  }
117
80
  export class OperationProcessingError extends Error {
81
+ code;
118
82
  constructor(message, options) {
119
83
  super(message, options);
120
84
  this.name = this.constructor.name;
85
+ if (options?.code) {
86
+ this.code = options?.code;
87
+ }
121
88
  Error.captureStackTrace?.(this, this.constructor);
122
89
  }
123
90
  }
124
- const OPE = OperationProcessingError;
125
- const dpopNonces = new LRU(100);
126
- function isCryptoKey(key) {
127
- return key instanceof CryptoKey;
128
- }
129
- function isPrivateKey(key) {
130
- return isCryptoKey(key) && key.type === 'private';
131
- }
132
- function isPublicKey(key) {
133
- return isCryptoKey(key) && key.type === 'public';
134
- }
135
- const SUPPORTED_JWS_ALGS = [
136
- 'PS256',
137
- 'ES256',
138
- 'RS256',
139
- 'PS384',
140
- 'ES384',
141
- 'RS384',
142
- 'PS512',
143
- 'ES512',
144
- 'RS512',
145
- 'EdDSA',
146
- ];
147
- function processDpopNonce(response) {
148
- try {
149
- const nonce = response.headers.get('dpop-nonce');
150
- if (nonce) {
151
- dpopNonces.set(new URL(response.url).origin, nonce);
152
- }
91
+ function OPE(message, code, cause) {
92
+ return new OperationProcessingError(message, { code, cause });
93
+ }
94
+ function assertCryptoKey(key, it) {
95
+ if (!(key instanceof CryptoKey)) {
96
+ throw CodedTypeError(`${it} must be a CryptoKey`, ERR_INVALID_ARG_TYPE);
97
+ }
98
+ }
99
+ function assertPrivateKey(key, it) {
100
+ assertCryptoKey(key, it);
101
+ if (key.type !== 'private') {
102
+ throw CodedTypeError(`${it} must be a private CryptoKey`, ERR_INVALID_ARG_VALUE);
103
+ }
104
+ }
105
+ function assertPublicKey(key, it) {
106
+ assertCryptoKey(key, it);
107
+ if (key.type !== 'public') {
108
+ throw CodedTypeError(`${it} must be a public CryptoKey`, ERR_INVALID_ARG_VALUE);
153
109
  }
154
- catch { }
155
- return response;
156
110
  }
157
111
  function normalizeTyp(value) {
158
112
  return value.toLowerCase().replace(/^application\//, '');
@@ -172,10 +126,10 @@ function prepareHeaders(input) {
172
126
  headers.set('user-agent', USER_AGENT);
173
127
  }
174
128
  if (headers.has('authorization')) {
175
- throw new TypeError('"options.headers" must not include the "authorization" header name');
129
+ throw CodedTypeError('"options.headers" must not include the "authorization" header name', ERR_INVALID_ARG_VALUE);
176
130
  }
177
131
  if (headers.has('dpop')) {
178
- throw new TypeError('"options.headers" must not include the "dpop" header name');
132
+ throw CodedTypeError('"options.headers" must not include the "dpop" header name', ERR_INVALID_ARG_VALUE);
179
133
  }
180
134
  return headers;
181
135
  }
@@ -184,17 +138,15 @@ function signal(value) {
184
138
  value = value();
185
139
  }
186
140
  if (!(value instanceof AbortSignal)) {
187
- throw new TypeError('"options.signal" must return or be an instance of AbortSignal');
141
+ throw CodedTypeError('"options.signal" must return or be an instance of AbortSignal', ERR_INVALID_ARG_TYPE);
188
142
  }
189
143
  return value;
190
144
  }
191
145
  export async function discoveryRequest(issuerIdentifier, options) {
192
146
  if (!(issuerIdentifier instanceof URL)) {
193
- throw new TypeError('"issuerIdentifier" must be an instance of URL');
194
- }
195
- if (issuerIdentifier.protocol !== 'https:' && issuerIdentifier.protocol !== 'http:') {
196
- throw new TypeError('"issuer.protocol" must be "https:" or "http:"');
147
+ throw CodedTypeError('"issuerIdentifier" must be an instance of URL', ERR_INVALID_ARG_TYPE);
197
148
  }
149
+ checkProtocol(issuerIdentifier, options?.[allowInsecureRequests] !== true);
198
150
  const url = new URL(issuerIdentifier.href);
199
151
  switch (options?.algorithm) {
200
152
  case undefined:
@@ -210,49 +162,110 @@ export async function discoveryRequest(issuerIdentifier, options) {
210
162
  }
211
163
  break;
212
164
  default:
213
- throw new TypeError('"options.algorithm" must be "oidc" (default), or "oauth2"');
165
+ throw CodedTypeError('"options.algorithm" must be "oidc" (default), or "oauth2"', ERR_INVALID_ARG_VALUE);
214
166
  }
215
167
  const headers = prepareHeaders(options?.headers);
216
168
  headers.set('accept', 'application/json');
217
169
  return (options?.[customFetch] || fetch)(url.href, {
170
+ body: undefined,
218
171
  headers: Object.fromEntries(headers.entries()),
219
172
  method: 'GET',
220
173
  redirect: 'manual',
221
- signal: options?.signal ? signal(options.signal) : null,
222
- }).then(processDpopNonce);
174
+ signal: options?.signal ? signal(options.signal) : undefined,
175
+ });
223
176
  }
224
- function validateString(input) {
225
- return typeof input === 'string' && input.length !== 0;
177
+ function assertNumber(input, allow0, it, code, cause) {
178
+ try {
179
+ if (typeof input !== 'number' || !Number.isFinite(input)) {
180
+ throw CodedTypeError(`${it} must be a number`, ERR_INVALID_ARG_TYPE, cause);
181
+ }
182
+ if (input > 0)
183
+ return;
184
+ if (allow0 && input !== 0) {
185
+ throw CodedTypeError(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE, cause);
186
+ }
187
+ throw CodedTypeError(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE, cause);
188
+ }
189
+ catch (err) {
190
+ if (code) {
191
+ throw OPE(err.message, code, cause);
192
+ }
193
+ throw err;
194
+ }
195
+ }
196
+ function assertString(input, it, code, cause) {
197
+ try {
198
+ if (typeof input !== 'string') {
199
+ throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE, cause);
200
+ }
201
+ if (input.length === 0) {
202
+ throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE, cause);
203
+ }
204
+ }
205
+ catch (err) {
206
+ if (code) {
207
+ throw OPE(err.message, code, cause);
208
+ }
209
+ throw err;
210
+ }
226
211
  }
227
212
  export async function processDiscoveryResponse(expectedIssuerIdentifier, response) {
228
- if (!(expectedIssuerIdentifier instanceof URL)) {
229
- throw new TypeError('"expectedIssuer" must be an instance of URL');
213
+ if (!(expectedIssuerIdentifier instanceof URL) &&
214
+ expectedIssuerIdentifier !== _nodiscoverycheck) {
215
+ throw CodedTypeError('"expectedIssuer" must be an instance of URL', ERR_INVALID_ARG_TYPE);
230
216
  }
231
217
  if (!looseInstanceOf(response, Response)) {
232
- throw new TypeError('"response" must be an instance of Response');
218
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
233
219
  }
234
220
  if (response.status !== 200) {
235
- throw new OPE('"response" is not a conform Authorization Server Metadata response');
221
+ throw OPE('"response" is not a conform Authorization Server Metadata response', RESPONSE_IS_NOT_CONFORM, response);
236
222
  }
237
223
  assertReadableResponse(response);
224
+ assertApplicationJson(response);
238
225
  let json;
239
226
  try {
240
227
  json = await response.json();
241
228
  }
242
229
  catch (cause) {
243
- throw new OPE('failed to parse "response" body as JSON', { cause });
230
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
244
231
  }
245
232
  if (!isJsonObject(json)) {
246
- throw new OPE('"response" body must be a top level object');
247
- }
248
- if (!validateString(json.issuer)) {
249
- throw new OPE('"response" body "issuer" property must be a non-empty string');
233
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
250
234
  }
251
- if (new URL(json.issuer).href !== expectedIssuerIdentifier.href) {
252
- throw new OPE('"response" body "issuer" does not match "expectedIssuer"');
235
+ assertString(json.issuer, '"response" body "issuer" property', INVALID_RESPONSE, { body: json });
236
+ if (new URL(json.issuer).href !== expectedIssuerIdentifier.href &&
237
+ expectedIssuerIdentifier !== _nodiscoverycheck) {
238
+ throw OPE('"response" body "issuer" property does not match the expected value', JSON_ATTRIBUTE_COMPARISON, { expected: expectedIssuerIdentifier.href, body: json, attribute: 'issuer' });
253
239
  }
254
240
  return json;
255
241
  }
242
+ function assertApplicationJson(response) {
243
+ assertContentType(response, 'application/json');
244
+ }
245
+ function notJson(response, ...types) {
246
+ let msg = '"response" content-type must be ';
247
+ if (types.length > 2) {
248
+ const last = types.pop();
249
+ msg += `${types.join(', ')}, or ${last}`;
250
+ }
251
+ else if (types.length === 2) {
252
+ msg += `${types[0]} or ${types[1]}`;
253
+ }
254
+ else {
255
+ msg += types[0];
256
+ }
257
+ return OPE(msg, RESPONSE_IS_NOT_JSON, response);
258
+ }
259
+ function assertContentTypes(response, ...types) {
260
+ if (!types.includes(getContentType(response))) {
261
+ throw notJson(response, ...types);
262
+ }
263
+ }
264
+ function assertContentType(response, contentType) {
265
+ if (getContentType(response) !== contentType) {
266
+ throw notJson(response, contentType);
267
+ }
268
+ }
256
269
  function randomBytes() {
257
270
  return b64u(crypto.getRandomValues(new Uint8Array(32)));
258
271
  }
@@ -266,9 +279,7 @@ export function generateRandomNonce() {
266
279
  return randomBytes();
267
280
  }
268
281
  export async function calculatePKCECodeChallenge(codeVerifier) {
269
- if (!validateString(codeVerifier)) {
270
- throw new TypeError('"codeVerifier" must be a non-empty string');
271
- }
282
+ assertString(codeVerifier, 'codeVerifier');
272
283
  return b64u(await crypto.subtle.digest('SHA-256', buf(codeVerifier)));
273
284
  }
274
285
  function getKeyAndKid(input) {
@@ -278,24 +289,14 @@ function getKeyAndKid(input) {
278
289
  if (!(input?.key instanceof CryptoKey)) {
279
290
  return {};
280
291
  }
281
- if (input.kid !== undefined && !validateString(input.kid)) {
282
- throw new TypeError('"kid" must be a non-empty string');
292
+ if (input.kid !== undefined) {
293
+ assertString(input.kid, '"kid"');
283
294
  }
284
295
  return {
285
296
  key: input.key,
286
297
  kid: input.kid,
287
- modifyAssertion: input[modifyAssertion],
288
298
  };
289
299
  }
290
- function formUrlEncode(token) {
291
- return encodeURIComponent(token).replace(/%20/g, '+');
292
- }
293
- function clientSecretBasic(clientId, clientSecret) {
294
- const username = formUrlEncode(clientId);
295
- const password = formUrlEncode(clientSecret);
296
- const credentials = btoa(`${username}:${password}`);
297
- return `Basic ${credentials}`;
298
- }
299
300
  function psAlg(key) {
300
301
  switch (key.algorithm.hash.name) {
301
302
  case 'SHA-256':
@@ -305,7 +306,9 @@ function psAlg(key) {
305
306
  case 'SHA-512':
306
307
  return 'PS512';
307
308
  default:
308
- throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
309
+ throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name', {
310
+ cause: key,
311
+ });
309
312
  }
310
313
  }
311
314
  function rsAlg(key) {
@@ -317,7 +320,9 @@ function rsAlg(key) {
317
320
  case 'SHA-512':
318
321
  return 'RS512';
319
322
  default:
320
- throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
323
+ throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name', {
324
+ cause: key,
325
+ });
321
326
  }
322
327
  }
323
328
  function esAlg(key) {
@@ -329,7 +334,7 @@ function esAlg(key) {
329
334
  case 'P-521':
330
335
  return 'ES512';
331
336
  default:
332
- throw new UnsupportedOperationError('unsupported EcKeyAlgorithm namedCurve');
337
+ throw new UnsupportedOperationError('unsupported EcKeyAlgorithm namedCurve', { cause: key });
333
338
  }
334
339
  }
335
340
  function keyToJws(key) {
@@ -341,10 +346,10 @@ function keyToJws(key) {
341
346
  case 'ECDSA':
342
347
  return esAlg(key);
343
348
  case 'Ed25519':
344
- case 'Ed448':
345
- return 'EdDSA';
349
+ case 'EdDSA':
350
+ return 'Ed25519';
346
351
  default:
347
- throw new UnsupportedOperationError('unsupported CryptoKey algorithm name');
352
+ throw new UnsupportedOperationError('unsupported CryptoKey algorithm name', { cause: key });
348
353
  }
349
354
  }
350
355
  function getClockSkew(client) {
@@ -360,11 +365,59 @@ function getClockTolerance(client) {
360
365
  function epochTime() {
361
366
  return Math.floor(Date.now() / 1000);
362
367
  }
363
- function clientAssertion(as, client) {
368
+ function assertAs(as) {
369
+ if (typeof as !== 'object' || as === null) {
370
+ throw CodedTypeError('"as" must be an object', ERR_INVALID_ARG_TYPE);
371
+ }
372
+ assertString(as.issuer, '"as.issuer"');
373
+ }
374
+ function assertClient(client) {
375
+ if (typeof client !== 'object' || client === null) {
376
+ throw CodedTypeError('"client" must be an object', ERR_INVALID_ARG_TYPE);
377
+ }
378
+ assertString(client.client_id, '"client.client_id"');
379
+ }
380
+ function formUrlEncode(token) {
381
+ return encodeURIComponent(token).replace(/(?:[-_.!~*'()]|%20)/g, (substring) => {
382
+ switch (substring) {
383
+ case '-':
384
+ case '_':
385
+ case '.':
386
+ case '!':
387
+ case '~':
388
+ case '*':
389
+ case "'":
390
+ case '(':
391
+ case ')':
392
+ return `%${substring.charCodeAt(0).toString(16).toUpperCase()}`;
393
+ case '%20':
394
+ return '+';
395
+ default:
396
+ throw new Error();
397
+ }
398
+ });
399
+ }
400
+ export function ClientSecretPost(clientSecret) {
401
+ assertString(clientSecret, '"clientSecret"');
402
+ return (_as, client, body, _headers) => {
403
+ body.set('client_id', client.client_id);
404
+ body.set('client_secret', clientSecret);
405
+ };
406
+ }
407
+ export function ClientSecretBasic(clientSecret) {
408
+ assertString(clientSecret, '"clientSecret"');
409
+ return (_as, client, _body, headers) => {
410
+ const username = formUrlEncode(client.client_id);
411
+ const password = formUrlEncode(clientSecret);
412
+ const credentials = btoa(`${username}:${password}`);
413
+ headers.set('authorization', `Basic ${credentials}`);
414
+ };
415
+ }
416
+ function clientAssertionPayload(as, client) {
364
417
  const now = epochTime() + getClockSkew(client);
365
418
  return {
366
419
  jti: randomBytes(),
367
- aud: [as.issuer, as.token_endpoint],
420
+ aud: as.issuer,
368
421
  exp: now + 60,
369
422
  iat: now,
370
423
  nbf: now,
@@ -372,105 +425,56 @@ function clientAssertion(as, client) {
372
425
  sub: client.client_id,
373
426
  };
374
427
  }
375
- async function privateKeyJwt(as, client, key, kid, modifyAssertion) {
376
- const header = { alg: keyToJws(key), kid };
377
- const payload = clientAssertion(as, client);
378
- modifyAssertion?.(header, payload);
379
- return jwt(header, payload, key);
380
- }
381
- function assertAs(as) {
382
- if (typeof as !== 'object' || as === null) {
383
- throw new TypeError('"as" must be an object');
384
- }
385
- if (!validateString(as.issuer)) {
386
- throw new TypeError('"as.issuer" property must be a non-empty string');
387
- }
388
- return true;
389
- }
390
- function assertClient(client) {
391
- if (typeof client !== 'object' || client === null) {
392
- throw new TypeError('"client" must be an object');
393
- }
394
- if (!validateString(client.client_id)) {
395
- throw new TypeError('"client.client_id" property must be a non-empty string');
396
- }
397
- return true;
398
- }
399
- function assertClientSecret(clientSecret) {
400
- if (!validateString(clientSecret)) {
401
- throw new TypeError('"client.client_secret" property must be a non-empty string');
402
- }
403
- return clientSecret;
428
+ export function PrivateKeyJwt(clientPrivateKey, options) {
429
+ const { key, kid } = getKeyAndKid(clientPrivateKey);
430
+ assertPrivateKey(key, '"clientPrivateKey.key"');
431
+ return async (as, client, body, _headers) => {
432
+ const header = { alg: keyToJws(key), kid };
433
+ const payload = clientAssertionPayload(as, client);
434
+ options?.[modifyAssertion]?.(header, payload);
435
+ body.set('client_id', client.client_id);
436
+ body.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
437
+ body.set('client_assertion', await signJwt(header, payload, key));
438
+ };
404
439
  }
405
- function assertNoClientPrivateKey(clientAuthMethod, clientPrivateKey) {
406
- if (clientPrivateKey !== undefined) {
407
- throw new TypeError(`"options.clientPrivateKey" property must not be provided when ${clientAuthMethod} client authentication method is used.`);
408
- }
440
+ export function ClientSecretJwt(clientSecret, options) {
441
+ assertString(clientSecret, '"clientSecret"');
442
+ const modify = options?.[modifyAssertion];
443
+ let key;
444
+ return async (as, client, body, _headers) => {
445
+ key ||= await crypto.subtle.importKey('raw', buf(clientSecret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);
446
+ const header = { alg: 'HS256' };
447
+ const payload = clientAssertionPayload(as, client);
448
+ modify?.(header, payload);
449
+ const data = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(payload)))}`;
450
+ const hmac = await crypto.subtle.sign(key.algorithm, key, buf(data));
451
+ body.set('client_id', client.client_id);
452
+ body.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
453
+ body.set('client_assertion', `${data}.${b64u(new Uint8Array(hmac))}`);
454
+ };
409
455
  }
410
- function assertNoClientSecret(clientAuthMethod, clientSecret) {
411
- if (clientSecret !== undefined) {
412
- throw new TypeError(`"client.client_secret" property must not be provided when ${clientAuthMethod} client authentication method is used.`);
413
- }
456
+ export function None() {
457
+ return (_as, client, body, _headers) => {
458
+ body.set('client_id', client.client_id);
459
+ };
414
460
  }
415
- async function clientAuthentication(as, client, body, headers, clientPrivateKey) {
416
- body.delete('client_secret');
417
- body.delete('client_assertion_type');
418
- body.delete('client_assertion');
419
- switch (client.token_endpoint_auth_method) {
420
- case undefined:
421
- case 'client_secret_basic': {
422
- assertNoClientPrivateKey('client_secret_basic', clientPrivateKey);
423
- headers.set('authorization', clientSecretBasic(client.client_id, assertClientSecret(client.client_secret)));
424
- break;
425
- }
426
- case 'client_secret_post': {
427
- assertNoClientPrivateKey('client_secret_post', clientPrivateKey);
428
- body.set('client_id', client.client_id);
429
- body.set('client_secret', assertClientSecret(client.client_secret));
430
- break;
431
- }
432
- case 'private_key_jwt': {
433
- assertNoClientSecret('private_key_jwt', client.client_secret);
434
- if (clientPrivateKey === undefined) {
435
- throw new TypeError('"options.clientPrivateKey" must be provided when "client.token_endpoint_auth_method" is "private_key_jwt"');
436
- }
437
- const { key, kid, modifyAssertion } = getKeyAndKid(clientPrivateKey);
438
- if (!isPrivateKey(key)) {
439
- throw new TypeError('"options.clientPrivateKey.key" must be a private CryptoKey');
440
- }
441
- body.set('client_id', client.client_id);
442
- body.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
443
- body.set('client_assertion', await privateKeyJwt(as, client, key, kid, modifyAssertion));
444
- break;
445
- }
446
- case 'tls_client_auth':
447
- case 'self_signed_tls_client_auth':
448
- case 'none': {
449
- assertNoClientSecret(client.token_endpoint_auth_method, client.client_secret);
450
- assertNoClientPrivateKey(client.token_endpoint_auth_method, clientPrivateKey);
451
- body.set('client_id', client.client_id);
452
- break;
453
- }
454
- default:
455
- throw new UnsupportedOperationError('unsupported client token_endpoint_auth_method');
456
- }
461
+ export function TlsClientAuth() {
462
+ return None();
457
463
  }
458
- async function jwt(header, payload, key) {
464
+ async function signJwt(header, payload, key) {
459
465
  if (!key.usages.includes('sign')) {
460
- throw new TypeError('CryptoKey instances used for signing assertions must include "sign" in their "usages"');
466
+ throw CodedTypeError('CryptoKey instances used for signing assertions must include "sign" in their "usages"', ERR_INVALID_ARG_VALUE);
461
467
  }
462
468
  const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(payload)))}`;
463
469
  const signature = b64u(await crypto.subtle.sign(keyToSubtle(key), key, buf(input)));
464
470
  return `${input}.${signature}`;
465
471
  }
466
- export async function issueRequestObject(as, client, parameters, privateKey) {
472
+ export async function issueRequestObject(as, client, parameters, privateKey, options) {
467
473
  assertAs(as);
468
474
  assertClient(client);
469
475
  parameters = new URLSearchParams(parameters);
470
- const { key, kid, modifyAssertion } = getKeyAndKid(privateKey);
471
- if (!isPrivateKey(key)) {
472
- throw new TypeError('"privateKey.key" must be a private CryptoKey');
473
- }
476
+ const { key, kid } = getKeyAndKid(privateKey);
477
+ assertPrivateKey(key, '"privateKey.key"');
474
478
  parameters.set('client_id', client.client_id);
475
479
  const now = epochTime() + getClockSkew(client);
476
480
  const claims = {
@@ -492,9 +496,7 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
492
496
  let value = parameters.get('max_age');
493
497
  if (value !== null) {
494
498
  claims.max_age = parseInt(value, 10);
495
- if (!Number.isFinite(claims.max_age)) {
496
- throw new OPE('"max_age" parameter must be a number');
497
- }
499
+ assertNumber(claims.max_age, true, '"max_age" parameter');
498
500
  }
499
501
  }
500
502
  {
@@ -504,10 +506,10 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
504
506
  claims.claims = JSON.parse(value);
505
507
  }
506
508
  catch (cause) {
507
- throw new OPE('failed to parse the "claims" parameter as JSON', { cause });
509
+ throw OPE('failed to parse the "claims" parameter as JSON', PARSE_ERROR, cause);
508
510
  }
509
511
  if (!isJsonObject(claims.claims)) {
510
- throw new OPE('"claims" parameter must be a JSON with a top level object');
512
+ throw CodedTypeError('"claims" parameter must be a JSON with a top level object', ERR_INVALID_ARG_VALUE);
511
513
  }
512
514
  }
513
515
  }
@@ -518,10 +520,10 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
518
520
  claims.authorization_details = JSON.parse(value);
519
521
  }
520
522
  catch (cause) {
521
- throw new OPE('failed to parse the "authorization_details" parameter as JSON', { cause });
523
+ throw OPE('failed to parse the "authorization_details" parameter as JSON', PARSE_ERROR, cause);
522
524
  }
523
525
  if (!Array.isArray(claims.authorization_details)) {
524
- throw new OPE('"authorization_details" parameter must be a JSON with a top level array');
526
+ throw CodedTypeError('"authorization_details" parameter must be a JSON with a top level array', ERR_INVALID_ARG_VALUE);
525
527
  }
526
528
  }
527
529
  }
@@ -530,39 +532,8 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
530
532
  typ: 'oauth-authz-req+jwt',
531
533
  kid,
532
534
  };
533
- modifyAssertion?.(header, claims);
534
- return jwt(header, claims, key);
535
- }
536
- async function dpopProofJwt(headers, options, url, htm, clockSkew, accessToken) {
537
- const { privateKey, publicKey, nonce = dpopNonces.get(url.origin) } = options;
538
- if (!isPrivateKey(privateKey)) {
539
- throw new TypeError('"DPoP.privateKey" must be a private CryptoKey');
540
- }
541
- if (!isPublicKey(publicKey)) {
542
- throw new TypeError('"DPoP.publicKey" must be a public CryptoKey');
543
- }
544
- if (nonce !== undefined && !validateString(nonce)) {
545
- throw new TypeError('"DPoP.nonce" must be a non-empty string or undefined');
546
- }
547
- if (!publicKey.extractable) {
548
- throw new TypeError('"DPoP.publicKey.extractable" must be true');
549
- }
550
- const now = epochTime() + clockSkew;
551
- const header = {
552
- alg: keyToJws(privateKey),
553
- typ: 'dpop+jwt',
554
- jwk: await publicJwk(publicKey),
555
- };
556
- const payload = {
557
- iat: now,
558
- jti: randomBytes(),
559
- htm,
560
- nonce,
561
- htu: `${url.origin}${url.pathname}`,
562
- ath: accessToken ? b64u(await crypto.subtle.digest('SHA-256', buf(accessToken))) : undefined,
563
- };
564
- options[modifyAssertion]?.(header, payload);
565
- headers.set('dpop', await jwt(header, payload, privateKey));
535
+ options?.[modifyAssertion]?.(header, claims);
536
+ return signJwt(header, claims, key);
566
537
  }
567
538
  let jwkCache;
568
539
  async function getSetPublicJwkCache(key) {
@@ -572,49 +543,185 @@ async function getSetPublicJwkCache(key) {
572
543
  return jwk;
573
544
  }
574
545
  async function publicJwk(key) {
575
- jwkCache || (jwkCache = new WeakMap());
546
+ jwkCache ||= new WeakMap();
576
547
  return jwkCache.get(key) || getSetPublicJwkCache(key);
577
548
  }
578
- function validateEndpoint(value, endpoint, useMtlsAlias) {
579
- if (typeof value !== 'string') {
580
- if (useMtlsAlias) {
581
- throw new TypeError(`"as.mtls_endpoint_aliases.${endpoint}" must be a string`);
549
+ const URLParse = URL.parse
550
+ ?
551
+ (url, base) => URL.parse(url, base)
552
+ : (url, base) => {
553
+ try {
554
+ return new URL(url, base);
582
555
  }
583
- throw new TypeError(`"as.${endpoint}" must be a string`);
556
+ catch {
557
+ return null;
558
+ }
559
+ };
560
+ export function checkProtocol(url, enforceHttps) {
561
+ if (enforceHttps && url.protocol !== 'https:') {
562
+ throw OPE('only requests to HTTPS are allowed', HTTP_REQUEST_FORBIDDEN, url);
563
+ }
564
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
565
+ throw OPE('only HTTP and HTTPS requests are allowed', REQUEST_PROTOCOL_FORBIDDEN, url);
584
566
  }
585
- return new URL(value);
586
567
  }
587
- function resolveEndpoint(as, endpoint, useMtlsAlias = false) {
588
- if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
589
- return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias);
568
+ function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
569
+ let url;
570
+ if (typeof value !== 'string' || !(url = URLParse(value))) {
571
+ throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === undefined ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
590
572
  }
591
- return validateEndpoint(as[endpoint], endpoint, useMtlsAlias);
573
+ checkProtocol(url, enforceHttps);
574
+ return url;
592
575
  }
593
- function alias(client, options) {
594
- if (client.use_mtls_endpoint_aliases || options?.[useMtlsAlias]) {
595
- return true;
576
+ export function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
577
+ if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
578
+ return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
596
579
  }
597
- return false;
580
+ return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
598
581
  }
599
- export async function pushedAuthorizationRequest(as, client, parameters, options) {
582
+ export async function pushedAuthorizationRequest(as, client, clientAuthentication, parameters, options) {
600
583
  assertAs(as);
601
584
  assertClient(client);
602
- const url = resolveEndpoint(as, 'pushed_authorization_request_endpoint', alias(client, options));
585
+ const url = resolveEndpoint(as, 'pushed_authorization_request_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
603
586
  const body = new URLSearchParams(parameters);
604
587
  body.set('client_id', client.client_id);
605
588
  const headers = prepareHeaders(options?.headers);
606
589
  headers.set('accept', 'application/json');
607
590
  if (options?.DPoP !== undefined) {
608
- await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client));
591
+ assertDPoP(options.DPoP);
592
+ await options.DPoP.addProof(url, headers, 'POST');
609
593
  }
610
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
594
+ const response = await authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
595
+ options?.DPoP?.cacheNonce(response);
596
+ return response;
611
597
  }
612
- export function isOAuth2Error(input) {
613
- const value = input;
614
- if (typeof value !== 'object' || Array.isArray(value) || value === null) {
615
- return false;
598
+ class DPoPHandler {
599
+ #header;
600
+ #privateKey;
601
+ #publicKey;
602
+ #clockSkew;
603
+ #modifyAssertion;
604
+ #map;
605
+ constructor(client, keyPair, options) {
606
+ assertPrivateKey(keyPair?.privateKey, '"DPoP.privateKey"');
607
+ assertPublicKey(keyPair?.publicKey, '"DPoP.publicKey"');
608
+ if (!keyPair.publicKey.extractable) {
609
+ throw CodedTypeError('"DPoP.publicKey.extractable" must be true', ERR_INVALID_ARG_VALUE);
610
+ }
611
+ this.#modifyAssertion = options?.[modifyAssertion];
612
+ this.#clockSkew = getClockSkew(client);
613
+ this.#privateKey = keyPair.privateKey;
614
+ this.#publicKey = keyPair.publicKey;
615
+ branded.add(this);
616
+ }
617
+ #get(key) {
618
+ this.#map ||= new Map();
619
+ let item = this.#map.get(key);
620
+ if (item) {
621
+ this.#map.delete(key);
622
+ this.#map.set(key, item);
623
+ }
624
+ return item;
625
+ }
626
+ #set(key, val) {
627
+ this.#map ||= new Map();
628
+ this.#map.delete(key);
629
+ if (this.#map.size === 100) {
630
+ this.#map.delete(this.#map.keys().next().value);
631
+ }
632
+ this.#map.set(key, val);
633
+ }
634
+ async addProof(url, headers, htm, accessToken) {
635
+ this.#header ||= {
636
+ alg: keyToJws(this.#privateKey),
637
+ typ: 'dpop+jwt',
638
+ jwk: await publicJwk(this.#publicKey),
639
+ };
640
+ const nonce = this.#get(url.origin);
641
+ const now = epochTime() + this.#clockSkew;
642
+ const payload = {
643
+ iat: now,
644
+ jti: randomBytes(),
645
+ htm,
646
+ nonce,
647
+ htu: `${url.origin}${url.pathname}`,
648
+ ath: accessToken ? b64u(await crypto.subtle.digest('SHA-256', buf(accessToken))) : undefined,
649
+ };
650
+ this.#modifyAssertion?.(this.#header, payload);
651
+ headers.set('dpop', await signJwt(this.#header, payload, this.#privateKey));
652
+ }
653
+ cacheNonce(response) {
654
+ try {
655
+ const nonce = response.headers.get('dpop-nonce');
656
+ if (nonce) {
657
+ this.#set(new URL(response.url).origin, nonce);
658
+ }
659
+ }
660
+ catch { }
661
+ }
662
+ }
663
+ export function isDPoPNonceError(err) {
664
+ if (err instanceof WWWAuthenticateChallengeError) {
665
+ const { 0: challenge, length } = err.cause;
666
+ return (length === 1 && challenge.scheme === 'dpop' && challenge.parameters.error === 'use_dpop_nonce');
667
+ }
668
+ if (err instanceof ResponseBodyError) {
669
+ return err.error === 'use_dpop_nonce';
670
+ }
671
+ return false;
672
+ }
673
+ export function DPoP(client, keyPair, options) {
674
+ return new DPoPHandler(client, keyPair, options);
675
+ }
676
+ export class ResponseBodyError extends Error {
677
+ cause;
678
+ code;
679
+ error;
680
+ status;
681
+ error_description;
682
+ response;
683
+ constructor(message, options) {
684
+ super(message, options);
685
+ this.name = this.constructor.name;
686
+ this.code = RESPONSE_BODY_ERROR;
687
+ this.cause = options.cause;
688
+ this.error = options.cause.error;
689
+ this.status = options.response.status;
690
+ this.error_description = options.cause.error_description;
691
+ Object.defineProperty(this, 'response', { enumerable: false, value: options.response });
692
+ Error.captureStackTrace?.(this, this.constructor);
693
+ }
694
+ }
695
+ export class AuthorizationResponseError extends Error {
696
+ cause;
697
+ code;
698
+ error;
699
+ error_description;
700
+ constructor(message, options) {
701
+ super(message, options);
702
+ this.name = this.constructor.name;
703
+ this.code = AUTHORIZATION_RESPONSE_ERROR;
704
+ this.cause = options.cause;
705
+ this.error = options.cause.get('error');
706
+ this.error_description = options.cause.get('error_description') ?? undefined;
707
+ Error.captureStackTrace?.(this, this.constructor);
708
+ }
709
+ }
710
+ export class WWWAuthenticateChallengeError extends Error {
711
+ cause;
712
+ code;
713
+ response;
714
+ status;
715
+ constructor(message, options) {
716
+ super(message, options);
717
+ this.name = this.constructor.name;
718
+ this.code = WWW_AUTHENTICATE_CHALLENGE;
719
+ this.cause = options.cause;
720
+ this.status = options.response.status;
721
+ this.response = options.response;
722
+ Object.defineProperty(this, 'response', { enumerable: false });
723
+ Error.captureStackTrace?.(this, this.constructor);
616
724
  }
617
- return value.error !== undefined;
618
725
  }
619
726
  function unquote(value) {
620
727
  if (value.length >= 2 && value[0] === '"' && value[value.length - 1] === '"') {
@@ -646,9 +753,9 @@ function wwwAuth(scheme, params) {
646
753
  parameters,
647
754
  };
648
755
  }
649
- export function parseWwwAuthenticateChallenges(response) {
756
+ function parseWwwAuthenticateChallenges(response) {
650
757
  if (!looseInstanceOf(response, Response)) {
651
- throw new TypeError('"response" must be an instance of Response');
758
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
652
759
  }
653
760
  const header = response.headers.get('www-authenticate');
654
761
  if (header === null) {
@@ -678,61 +785,88 @@ export async function processPushedAuthorizationResponse(as, client, response) {
678
785
  assertAs(as);
679
786
  assertClient(client);
680
787
  if (!looseInstanceOf(response, Response)) {
681
- throw new TypeError('"response" must be an instance of Response');
788
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
789
+ }
790
+ let challenges;
791
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
792
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
682
793
  }
683
794
  if (response.status !== 201) {
684
795
  let err;
685
796
  if ((err = await handleOAuthBodyError(response))) {
686
- return err;
797
+ await response.body?.cancel();
798
+ throw new ResponseBodyError('server responded with an error in the response body', {
799
+ cause: err,
800
+ response,
801
+ });
687
802
  }
688
- throw new OPE('"response" is not a conform Pushed Authorization Request Endpoint response');
803
+ throw OPE('"response" is not a conform Pushed Authorization Request Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
689
804
  }
690
805
  assertReadableResponse(response);
806
+ assertApplicationJson(response);
691
807
  let json;
692
808
  try {
693
809
  json = await response.json();
694
810
  }
695
811
  catch (cause) {
696
- throw new OPE('failed to parse "response" body as JSON', { cause });
812
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
697
813
  }
698
814
  if (!isJsonObject(json)) {
699
- throw new OPE('"response" body must be a top level object');
700
- }
701
- if (!validateString(json.request_uri)) {
702
- throw new OPE('"response" body "request_uri" property must be a non-empty string');
703
- }
704
- if (typeof json.expires_in !== 'number' || json.expires_in <= 0) {
705
- throw new OPE('"response" body "expires_in" property must be a positive number');
815
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
706
816
  }
817
+ assertString(json.request_uri, '"response" body "request_uri" property', INVALID_RESPONSE, {
818
+ body: json,
819
+ });
820
+ let expiresIn = typeof json.expires_in !== 'number' ? parseFloat(json.expires_in) : json.expires_in;
821
+ assertNumber(expiresIn, false, '"response" body "expires_in" property', INVALID_RESPONSE, {
822
+ body: json,
823
+ });
824
+ json.expires_in = expiresIn;
707
825
  return json;
708
826
  }
709
- export async function protectedResourceRequest(accessToken, method, url, headers, body, options) {
710
- if (!validateString(accessToken)) {
711
- throw new TypeError('"accessToken" must be a non-empty string');
827
+ function assertDPoP(option) {
828
+ if (!branded.has(option)) {
829
+ throw CodedTypeError('"options.DPoP" is not a valid DPoPHandle', ERR_INVALID_ARG_VALUE);
712
830
  }
831
+ }
832
+ async function resourceRequest(accessToken, method, url, headers, body, options) {
833
+ assertString(accessToken, '"accessToken"');
713
834
  if (!(url instanceof URL)) {
714
- throw new TypeError('"url" must be an instance of URL');
835
+ throw CodedTypeError('"url" must be an instance of URL', ERR_INVALID_ARG_TYPE);
715
836
  }
837
+ checkProtocol(url, options?.[allowInsecureRequests] !== true);
716
838
  headers = prepareHeaders(headers);
717
- if (options?.DPoP === undefined) {
718
- headers.set('authorization', `Bearer ${accessToken}`);
839
+ if (options?.DPoP) {
840
+ assertDPoP(options.DPoP);
841
+ await options.DPoP.addProof(url, headers, method.toUpperCase(), accessToken);
842
+ headers.set('authorization', `DPoP ${accessToken}`);
719
843
  }
720
844
  else {
721
- await dpopProofJwt(headers, options.DPoP, url, method.toUpperCase(), getClockSkew({ [clockSkew]: options?.[clockSkew] }), accessToken);
722
- headers.set('authorization', `DPoP ${accessToken}`);
845
+ headers.set('authorization', `Bearer ${accessToken}`);
723
846
  }
724
- return (options?.[customFetch] || fetch)(url.href, {
847
+ const response = await (options?.[customFetch] || fetch)(url.href, {
725
848
  body,
726
849
  headers: Object.fromEntries(headers.entries()),
727
850
  method,
728
851
  redirect: 'manual',
729
- signal: options?.signal ? signal(options.signal) : null,
730
- }).then(processDpopNonce);
852
+ signal: options?.signal ? signal(options.signal) : undefined,
853
+ });
854
+ options?.DPoP?.cacheNonce(response);
855
+ return response;
856
+ }
857
+ export async function protectedResourceRequest(accessToken, method, url, headers, body, options) {
858
+ return resourceRequest(accessToken, method, url, headers, body, options).then((response) => {
859
+ let challenges;
860
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
861
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
862
+ }
863
+ return response;
864
+ });
731
865
  }
732
866
  export async function userInfoRequest(as, client, accessToken, options) {
733
867
  assertAs(as);
734
868
  assertClient(client);
735
- const url = resolveEndpoint(as, 'userinfo_endpoint', alias(client, options));
869
+ const url = resolveEndpoint(as, 'userinfo_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
736
870
  const headers = prepareHeaders(options?.headers);
737
871
  if (client.userinfo_signed_response_alg) {
738
872
  headers.set('accept', 'application/jwt');
@@ -741,14 +875,14 @@ export async function userInfoRequest(as, client, accessToken, options) {
741
875
  headers.set('accept', 'application/json');
742
876
  headers.append('accept', 'application/jwt');
743
877
  }
744
- return protectedResourceRequest(accessToken, 'GET', url, headers, null, {
878
+ return resourceRequest(accessToken, 'GET', url, headers, null, {
745
879
  ...options,
746
880
  [clockSkew]: getClockSkew(client),
747
881
  });
748
882
  }
749
883
  let jwksMap;
750
884
  function setJwksCache(as, jwks, uat, cache) {
751
- jwksMap || (jwksMap = new WeakMap());
885
+ jwksMap ||= new WeakMap();
752
886
  jwksMap.set(as, {
753
887
  jwks,
754
888
  uat,
@@ -782,7 +916,7 @@ function clearJwksCache(as, cache) {
782
916
  }
783
917
  async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
784
918
  const { alg, kid } = header;
785
- checkSupportedJwsAlg(alg);
919
+ checkSupportedJwsAlg(header);
786
920
  if (!jwksMap?.has(as) && isFreshJwksCache(options?.[jwksCache])) {
787
921
  setJwksCache(as, options?.[jwksCache].jwks, options?.[jwksCache].uat);
788
922
  }
@@ -814,7 +948,7 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
814
948
  kty = 'OKP';
815
949
  break;
816
950
  default:
817
- throw new UnsupportedOperationError();
951
+ throw new UnsupportedOperationError('unsupported JWS algorithm', { cause: { alg } });
818
952
  }
819
953
  const candidates = jwks.keys.filter((jwk) => {
820
954
  if (jwk.kty !== kty) {
@@ -836,7 +970,8 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
836
970
  case alg === 'ES256' && jwk.crv !== 'P-256':
837
971
  case alg === 'ES384' && jwk.crv !== 'P-384':
838
972
  case alg === 'ES512' && jwk.crv !== 'P-521':
839
- case alg === 'EdDSA' && !(jwk.crv === 'Ed25519' || jwk.crv === 'Ed448'):
973
+ case alg === 'Ed25519' && jwk.crv !== 'Ed25519':
974
+ case alg === 'EdDSA' && jwk.crv !== 'Ed25519':
840
975
  return false;
841
976
  }
842
977
  return true;
@@ -847,221 +982,223 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
847
982
  clearJwksCache(as, options?.[jwksCache]);
848
983
  return getPublicSigKeyFromIssuerJwksUri(as, options, header);
849
984
  }
850
- throw new OPE('error when selecting a JWT verification key, no applicable keys found');
985
+ throw OPE('error when selecting a JWT verification key, no applicable keys found', KEY_SELECTION, { header, candidates, jwks_uri: new URL(as.jwks_uri) });
851
986
  }
852
987
  if (length !== 1) {
853
- throw new OPE('error when selecting a JWT verification key, multiple applicable keys found, a "kid" JWT Header Parameter is required');
854
- }
855
- const key = await importJwk(alg, jwk);
856
- if (key.type !== 'public') {
857
- throw new OPE('jwks_uri must only contain public keys');
988
+ throw OPE('error when selecting a JWT verification key, multiple applicable keys found, a "kid" JWT Header Parameter is required', KEY_SELECTION, { header, candidates, jwks_uri: new URL(as.jwks_uri) });
858
989
  }
859
- return key;
990
+ return importJwk(alg, jwk);
860
991
  }
861
992
  export const skipSubjectCheck = Symbol();
862
993
  function getContentType(response) {
863
994
  return response.headers.get('content-type')?.split(';')[0];
864
995
  }
865
- export async function processUserInfoResponse(as, client, expectedSubject, response) {
996
+ export async function processUserInfoResponse(as, client, expectedSubject, response, options) {
866
997
  assertAs(as);
867
998
  assertClient(client);
868
999
  if (!looseInstanceOf(response, Response)) {
869
- throw new TypeError('"response" must be an instance of Response');
1000
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1001
+ }
1002
+ let challenges;
1003
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1004
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
870
1005
  }
871
1006
  if (response.status !== 200) {
872
- throw new OPE('"response" is not a conform UserInfo Endpoint response');
1007
+ throw OPE('"response" is not a conform UserInfo Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
873
1008
  }
1009
+ assertReadableResponse(response);
874
1010
  let json;
875
1011
  if (getContentType(response) === 'application/jwt') {
876
- assertReadableResponse(response);
877
- const { claims, jwt } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
1012
+ const { claims, jwt } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported, undefined), getClockSkew(client), getClockTolerance(client), options?.[jweDecrypt])
878
1013
  .then(validateOptionalAudience.bind(undefined, client.client_id))
879
- .then(validateOptionalIssuer.bind(undefined, as.issuer));
880
- jwtResponseBodies.set(response, jwt);
1014
+ .then(validateOptionalIssuer.bind(undefined, as));
1015
+ jwtRefs.set(response, jwt);
881
1016
  json = claims;
882
1017
  }
883
1018
  else {
884
1019
  if (client.userinfo_signed_response_alg) {
885
- throw new OPE('JWT UserInfo Response expected');
1020
+ throw OPE('JWT UserInfo Response expected', JWT_USERINFO_EXPECTED, response);
886
1021
  }
887
- assertReadableResponse(response);
1022
+ assertApplicationJson(response);
888
1023
  try {
889
1024
  json = await response.json();
890
1025
  }
891
1026
  catch (cause) {
892
- throw new OPE('failed to parse "response" body as JSON', { cause });
1027
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
893
1028
  }
894
1029
  }
895
1030
  if (!isJsonObject(json)) {
896
- throw new OPE('"response" body must be a top level object');
897
- }
898
- if (!validateString(json.sub)) {
899
- throw new OPE('"response" body "sub" property must be a non-empty string');
1031
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
900
1032
  }
1033
+ assertString(json.sub, '"response" body "sub" property', INVALID_RESPONSE, { body: json });
901
1034
  switch (expectedSubject) {
902
1035
  case skipSubjectCheck:
903
1036
  break;
904
1037
  default:
905
- if (!validateString(expectedSubject)) {
906
- throw new OPE('"expectedSubject" must be a non-empty string');
907
- }
1038
+ assertString(expectedSubject, '"expectedSubject"');
908
1039
  if (json.sub !== expectedSubject) {
909
- throw new OPE('unexpected "response" body "sub" value');
1040
+ throw OPE('unexpected "response" body "sub" property value', JSON_ATTRIBUTE_COMPARISON, {
1041
+ expected: expectedSubject,
1042
+ body: json,
1043
+ attribute: 'sub',
1044
+ });
910
1045
  }
911
1046
  }
912
1047
  return json;
913
1048
  }
914
- async function authenticatedRequest(as, client, method, url, body, headers, options) {
915
- await clientAuthentication(as, client, body, headers, options?.clientPrivateKey);
1049
+ async function authenticatedRequest(as, client, clientAuthentication, url, body, headers, options) {
1050
+ await clientAuthentication(as, client, body, headers);
916
1051
  headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
917
1052
  return (options?.[customFetch] || fetch)(url.href, {
918
1053
  body,
919
1054
  headers: Object.fromEntries(headers.entries()),
920
- method,
1055
+ method: 'POST',
921
1056
  redirect: 'manual',
922
- signal: options?.signal ? signal(options.signal) : null,
923
- }).then(processDpopNonce);
1057
+ signal: options?.signal ? signal(options.signal) : undefined,
1058
+ });
924
1059
  }
925
- async function tokenEndpointRequest(as, client, grantType, parameters, options) {
926
- const url = resolveEndpoint(as, 'token_endpoint', alias(client, options));
1060
+ async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
1061
+ const url = resolveEndpoint(as, 'token_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
927
1062
  parameters.set('grant_type', grantType);
928
1063
  const headers = prepareHeaders(options?.headers);
929
1064
  headers.set('accept', 'application/json');
930
1065
  if (options?.DPoP !== undefined) {
931
- await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client));
1066
+ assertDPoP(options.DPoP);
1067
+ await options.DPoP.addProof(url, headers, 'POST');
932
1068
  }
933
- return authenticatedRequest(as, client, 'POST', url, parameters, headers, options);
1069
+ const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers, options);
1070
+ options?.DPoP?.cacheNonce(response);
1071
+ return response;
934
1072
  }
935
- export async function refreshTokenGrantRequest(as, client, refreshToken, options) {
1073
+ export async function refreshTokenGrantRequest(as, client, clientAuthentication, refreshToken, options) {
936
1074
  assertAs(as);
937
1075
  assertClient(client);
938
- if (!validateString(refreshToken)) {
939
- throw new TypeError('"refreshToken" must be a non-empty string');
940
- }
1076
+ assertString(refreshToken, '"refreshToken"');
941
1077
  const parameters = new URLSearchParams(options?.additionalParameters);
942
1078
  parameters.set('refresh_token', refreshToken);
943
- return tokenEndpointRequest(as, client, 'refresh_token', parameters, options);
1079
+ return tokenEndpointRequest(as, client, clientAuthentication, 'refresh_token', parameters, options);
944
1080
  }
945
1081
  const idTokenClaims = new WeakMap();
946
- const jwtResponseBodies = new WeakMap();
1082
+ const jwtRefs = new WeakMap();
947
1083
  export function getValidatedIdTokenClaims(ref) {
948
1084
  if (!ref.id_token) {
949
1085
  return undefined;
950
1086
  }
951
1087
  const claims = idTokenClaims.get(ref);
952
1088
  if (!claims) {
953
- throw new TypeError('"ref" was already garbage collected or did not resolve from the proper sources');
954
- }
955
- return claims[0];
956
- }
957
- export async function validateIdTokenSignature(as, ref, options) {
958
- assertAs(as);
959
- if (!idTokenClaims.has(ref)) {
960
- throw new OPE('"ref" does not contain an ID Token to verify the signature of');
1089
+ throw CodedTypeError('"ref" was already garbage collected or did not resolve from the proper sources', ERR_INVALID_ARG_VALUE);
961
1090
  }
962
- const { 0: protectedHeader, 1: payload, 2: encodedSignature, } = idTokenClaims.get(ref)[1].split('.');
963
- const header = JSON.parse(buf(b64u(protectedHeader)));
964
- if (header.alg.startsWith('HS')) {
965
- throw new UnsupportedOperationError();
966
- }
967
- let key;
968
- key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
969
- await validateJwsSignature(protectedHeader, payload, key, b64u(encodedSignature));
1091
+ return claims;
970
1092
  }
971
- async function validateJwtResponseSignature(as, ref, options) {
1093
+ export async function validateApplicationLevelSignature(as, ref, options) {
972
1094
  assertAs(as);
973
- if (!jwtResponseBodies.has(ref)) {
974
- throw new OPE('"ref" does not contain a processed JWT Response to verify the signature of');
1095
+ if (!jwtRefs.has(ref)) {
1096
+ throw CodedTypeError('"ref" does not contain a processed JWT Response to verify the signature of', ERR_INVALID_ARG_VALUE);
975
1097
  }
976
- const { 0: protectedHeader, 1: payload, 2: encodedSignature, } = jwtResponseBodies.get(ref).split('.');
1098
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwtRefs.get(ref).split('.');
977
1099
  const header = JSON.parse(buf(b64u(protectedHeader)));
978
1100
  if (header.alg.startsWith('HS')) {
979
- throw new UnsupportedOperationError();
1101
+ throw new UnsupportedOperationError('unsupported JWS algorithm', { cause: { alg: header.alg } });
980
1102
  }
981
1103
  let key;
982
1104
  key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
983
1105
  await validateJwsSignature(protectedHeader, payload, key, b64u(encodedSignature));
984
1106
  }
985
- export function validateJwtUserInfoSignature(as, ref, options) {
986
- return validateJwtResponseSignature(as, ref, options);
987
- }
988
- export function validateJwtIntrospectionSignature(as, ref, options) {
989
- return validateJwtResponseSignature(as, ref, options);
990
- }
991
- async function processGenericAccessTokenResponse(as, client, response, ignoreIdToken = false, ignoreRefreshToken = false) {
1107
+ async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, options) {
992
1108
  assertAs(as);
993
1109
  assertClient(client);
994
1110
  if (!looseInstanceOf(response, Response)) {
995
- throw new TypeError('"response" must be an instance of Response');
1111
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1112
+ }
1113
+ let challenges;
1114
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1115
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
996
1116
  }
997
1117
  if (response.status !== 200) {
998
1118
  let err;
999
1119
  if ((err = await handleOAuthBodyError(response))) {
1000
- return err;
1120
+ await response.body?.cancel();
1121
+ throw new ResponseBodyError('server responded with an error in the response body', {
1122
+ cause: err,
1123
+ response,
1124
+ });
1001
1125
  }
1002
- throw new OPE('"response" is not a conform Token Endpoint response');
1126
+ throw OPE('"response" is not a conform Token Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1003
1127
  }
1004
1128
  assertReadableResponse(response);
1129
+ assertApplicationJson(response);
1005
1130
  let json;
1006
1131
  try {
1007
1132
  json = await response.json();
1008
1133
  }
1009
1134
  catch (cause) {
1010
- throw new OPE('failed to parse "response" body as JSON', { cause });
1135
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1011
1136
  }
1012
1137
  if (!isJsonObject(json)) {
1013
- throw new OPE('"response" body must be a top level object');
1014
- }
1015
- if (!validateString(json.access_token)) {
1016
- throw new OPE('"response" body "access_token" property must be a non-empty string');
1017
- }
1018
- if (!validateString(json.token_type)) {
1019
- throw new OPE('"response" body "token_type" property must be a non-empty string');
1138
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1020
1139
  }
1140
+ assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
1141
+ body: json,
1142
+ });
1143
+ assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
1144
+ body: json,
1145
+ });
1021
1146
  json.token_type = json.token_type.toLowerCase();
1022
1147
  if (json.token_type !== 'dpop' && json.token_type !== 'bearer') {
1023
- throw new UnsupportedOperationError('unsupported `token_type` value');
1148
+ throw new UnsupportedOperationError('unsupported `token_type` value', { cause: { body: json } });
1024
1149
  }
1025
- if (json.expires_in !== undefined &&
1026
- (typeof json.expires_in !== 'number' || json.expires_in <= 0)) {
1027
- throw new OPE('"response" body "expires_in" property must be a positive number');
1150
+ if (json.expires_in !== undefined) {
1151
+ let expiresIn = typeof json.expires_in !== 'number' ? parseFloat(json.expires_in) : json.expires_in;
1152
+ assertNumber(expiresIn, false, '"response" body "expires_in" property', INVALID_RESPONSE, {
1153
+ body: json,
1154
+ });
1155
+ json.expires_in = expiresIn;
1028
1156
  }
1029
- if (!ignoreRefreshToken &&
1030
- json.refresh_token !== undefined &&
1031
- !validateString(json.refresh_token)) {
1032
- throw new OPE('"response" body "refresh_token" property must be a non-empty string');
1157
+ if (json.refresh_token !== undefined) {
1158
+ assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
1159
+ body: json,
1160
+ });
1033
1161
  }
1034
1162
  if (json.scope !== undefined && typeof json.scope !== 'string') {
1035
- throw new OPE('"response" body "scope" property must be a string');
1036
- }
1037
- if (!ignoreIdToken) {
1038
- if (json.id_token !== undefined && !validateString(json.id_token)) {
1039
- throw new OPE('"response" body "id_token" property must be a non-empty string');
1040
- }
1041
- if (json.id_token) {
1042
- const { claims, jwt } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
1043
- .then(validatePresence.bind(undefined, ['aud', 'exp', 'iat', 'iss', 'sub']))
1044
- .then(validateIssuer.bind(undefined, as.issuer))
1045
- .then(validateAudience.bind(undefined, client.client_id));
1046
- if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
1047
- if (claims.azp === undefined) {
1048
- throw new OPE('ID Token "aud" (audience) claim includes additional untrusted audiences');
1049
- }
1050
- if (claims.azp !== client.client_id) {
1051
- throw new OPE('unexpected ID Token "azp" (authorized party) claim value');
1052
- }
1163
+ throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, { body: json });
1164
+ }
1165
+ if (json.id_token !== undefined) {
1166
+ assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
1167
+ body: json,
1168
+ });
1169
+ const requiredClaims = ['aud', 'exp', 'iat', 'iss', 'sub'];
1170
+ if (client.require_auth_time === true) {
1171
+ requiredClaims.push('auth_time');
1172
+ }
1173
+ if (client.default_max_age !== undefined) {
1174
+ assertNumber(client.default_max_age, false, '"client.default_max_age"');
1175
+ requiredClaims.push('auth_time');
1176
+ }
1177
+ if (additionalRequiredIdTokenClaims?.length) {
1178
+ requiredClaims.push(...additionalRequiredIdTokenClaims);
1179
+ }
1180
+ const { claims, jwt } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, 'RS256'), getClockSkew(client), getClockTolerance(client), options?.[jweDecrypt])
1181
+ .then(validatePresence.bind(undefined, requiredClaims))
1182
+ .then(validateIssuer.bind(undefined, as))
1183
+ .then(validateAudience.bind(undefined, client.client_id));
1184
+ if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
1185
+ if (claims.azp === undefined) {
1186
+ throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: 'aud' });
1053
1187
  }
1054
- if (claims.auth_time !== undefined &&
1055
- (!Number.isFinite(claims.auth_time) || Math.sign(claims.auth_time) !== 1)) {
1056
- throw new OPE('ID Token "auth_time" (authentication time) must be a positive number');
1188
+ if (claims.azp !== client.client_id) {
1189
+ throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, { expected: client.client_id, claims, claim: 'azp' });
1057
1190
  }
1058
- idTokenClaims.set(json, [claims, jwt]);
1059
1191
  }
1192
+ if (claims.auth_time !== undefined) {
1193
+ assertNumber(claims.auth_time, false, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
1194
+ }
1195
+ jwtRefs.set(response, jwt);
1196
+ idTokenClaims.set(json, claims);
1060
1197
  }
1061
1198
  return json;
1062
1199
  }
1063
- export async function processRefreshTokenResponse(as, client, response) {
1064
- return processGenericAccessTokenResponse(as, client, response);
1200
+ export async function processRefreshTokenResponse(as, client, response, options) {
1201
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1065
1202
  }
1066
1203
  function validateOptionalAudience(expected, result) {
1067
1204
  if (result.claims.aud !== undefined) {
@@ -1072,23 +1209,36 @@ function validateOptionalAudience(expected, result) {
1072
1209
  function validateAudience(expected, result) {
1073
1210
  if (Array.isArray(result.claims.aud)) {
1074
1211
  if (!result.claims.aud.includes(expected)) {
1075
- throw new OPE('unexpected JWT "aud" (audience) claim value');
1212
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
1213
+ expected,
1214
+ claims: result.claims,
1215
+ claim: 'aud',
1216
+ });
1076
1217
  }
1077
1218
  }
1078
1219
  else if (result.claims.aud !== expected) {
1079
- throw new OPE('unexpected JWT "aud" (audience) claim value');
1220
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
1221
+ expected,
1222
+ claims: result.claims,
1223
+ claim: 'aud',
1224
+ });
1080
1225
  }
1081
1226
  return result;
1082
1227
  }
1083
- function validateOptionalIssuer(expected, result) {
1228
+ function validateOptionalIssuer(as, result) {
1084
1229
  if (result.claims.iss !== undefined) {
1085
- return validateIssuer(expected, result);
1230
+ return validateIssuer(as, result);
1086
1231
  }
1087
1232
  return result;
1088
1233
  }
1089
- function validateIssuer(expected, result) {
1234
+ function validateIssuer(as, result) {
1235
+ const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
1090
1236
  if (result.claims.iss !== expected) {
1091
- throw new OPE('unexpected JWT "iss" (issuer) claim value');
1237
+ throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
1238
+ expected,
1239
+ claims: result.claims,
1240
+ claim: 'iss',
1241
+ });
1092
1242
  }
1093
1243
  return result;
1094
1244
  }
@@ -1097,27 +1247,25 @@ function brand(searchParams) {
1097
1247
  branded.add(searchParams);
1098
1248
  return searchParams;
1099
1249
  }
1100
- export async function authorizationCodeGrantRequest(as, client, callbackParameters, redirectUri, codeVerifier, options) {
1250
+ export async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
1101
1251
  assertAs(as);
1102
1252
  assertClient(client);
1103
1253
  if (!branded.has(callbackParameters)) {
1104
- throw new TypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()');
1105
- }
1106
- if (!validateString(redirectUri)) {
1107
- throw new TypeError('"redirectUri" must be a non-empty string');
1108
- }
1109
- if (!validateString(codeVerifier)) {
1110
- throw new TypeError('"codeVerifier" must be a non-empty string');
1254
+ throw CodedTypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()', ERR_INVALID_ARG_VALUE);
1111
1255
  }
1256
+ assertString(redirectUri, '"redirectUri"');
1112
1257
  const code = getURLSearchParameter(callbackParameters, 'code');
1113
1258
  if (!code) {
1114
- throw new OPE('no authorization code in "callbackParameters"');
1259
+ throw OPE('no authorization code in "callbackParameters"', INVALID_RESPONSE);
1115
1260
  }
1116
1261
  const parameters = new URLSearchParams(options?.additionalParameters);
1117
1262
  parameters.set('redirect_uri', redirectUri);
1118
- parameters.set('code_verifier', codeVerifier);
1119
1263
  parameters.set('code', code);
1120
- return tokenEndpointRequest(as, client, 'authorization_code', parameters, options);
1264
+ if (codeVerifier !== _nopkce) {
1265
+ assertString(codeVerifier, '"codeVerifier"');
1266
+ parameters.set('code_verifier', codeVerifier);
1267
+ }
1268
+ return tokenEndpointRequest(as, client, clientAuthentication, 'authorization_code', parameters, options);
1121
1269
  }
1122
1270
  const jwtClaimNames = {
1123
1271
  aud: 'audience',
@@ -1134,138 +1282,190 @@ const jwtClaimNames = {
1134
1282
  htm: 'http method',
1135
1283
  htu: 'http uri',
1136
1284
  cnf: 'confirmation',
1285
+ auth_time: 'authentication time',
1137
1286
  };
1138
1287
  function validatePresence(required, result) {
1139
1288
  for (const claim of required) {
1140
1289
  if (result.claims[claim] === undefined) {
1141
- throw new OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`);
1290
+ throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
1291
+ claims: result.claims,
1292
+ });
1142
1293
  }
1143
1294
  }
1144
1295
  return result;
1145
1296
  }
1146
1297
  export const expectNoNonce = Symbol();
1147
1298
  export const skipAuthTimeCheck = Symbol();
1148
- export async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge) {
1149
- const result = await processGenericAccessTokenResponse(as, client, response);
1150
- if (isOAuth2Error(result)) {
1151
- return result;
1299
+ export async function processAuthorizationCodeResponse(as, client, response, options) {
1300
+ if (typeof options?.expectedNonce === 'string' ||
1301
+ typeof options?.maxAge === 'number' ||
1302
+ options?.requireIdToken) {
1303
+ return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, {
1304
+ [jweDecrypt]: options[jweDecrypt],
1305
+ });
1152
1306
  }
1153
- if (!validateString(result.id_token)) {
1154
- throw new OPE('"response" body "id_token" property must be a non-empty string');
1307
+ return processAuthorizationCodeOAuth2Response(as, client, response, options);
1308
+ }
1309
+ async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, options) {
1310
+ const additionalRequiredClaims = [];
1311
+ switch (expectedNonce) {
1312
+ case undefined:
1313
+ expectedNonce = expectNoNonce;
1314
+ break;
1315
+ case expectNoNonce:
1316
+ break;
1317
+ default:
1318
+ assertString(expectedNonce, '"expectedNonce" argument');
1319
+ additionalRequiredClaims.push('nonce');
1155
1320
  }
1156
- maxAge ?? (maxAge = client.default_max_age ?? skipAuthTimeCheck);
1157
- const claims = getValidatedIdTokenClaims(result);
1158
- if ((client.require_auth_time || maxAge !== skipAuthTimeCheck) &&
1159
- claims.auth_time === undefined) {
1160
- throw new OPE('ID Token "auth_time" (authentication time) claim missing');
1321
+ maxAge ??= client.default_max_age;
1322
+ switch (maxAge) {
1323
+ case undefined:
1324
+ maxAge = skipAuthTimeCheck;
1325
+ break;
1326
+ case skipAuthTimeCheck:
1327
+ break;
1328
+ default:
1329
+ assertNumber(maxAge, false, '"maxAge" argument');
1330
+ additionalRequiredClaims.push('auth_time');
1161
1331
  }
1332
+ const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, options);
1333
+ assertString(result.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
1334
+ body: result,
1335
+ });
1336
+ const claims = getValidatedIdTokenClaims(result);
1162
1337
  if (maxAge !== skipAuthTimeCheck) {
1163
- if (typeof maxAge !== 'number' || maxAge < 0) {
1164
- throw new TypeError('"maxAge" must be a non-negative number');
1165
- }
1166
1338
  const now = epochTime() + getClockSkew(client);
1167
1339
  const tolerance = getClockTolerance(client);
1168
1340
  if (claims.auth_time + maxAge < now - tolerance) {
1169
- throw new OPE('too much time has elapsed since the last End-User authentication');
1341
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: 'auth_time' });
1170
1342
  }
1171
1343
  }
1172
- switch (expectedNonce) {
1173
- case undefined:
1174
- case expectNoNonce:
1175
- if (claims.nonce !== undefined) {
1176
- throw new OPE('unexpected ID Token "nonce" claim value');
1177
- }
1178
- break;
1179
- default:
1180
- if (!validateString(expectedNonce)) {
1181
- throw new TypeError('"expectedNonce" must be a non-empty string');
1182
- }
1183
- if (claims.nonce === undefined) {
1184
- throw new OPE('ID Token "nonce" claim missing');
1185
- }
1186
- if (claims.nonce !== expectedNonce) {
1187
- throw new OPE('unexpected ID Token "nonce" claim value');
1188
- }
1344
+ if (expectedNonce === expectNoNonce) {
1345
+ if (claims.nonce !== undefined) {
1346
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1347
+ expected: undefined,
1348
+ claims,
1349
+ claim: 'nonce',
1350
+ });
1351
+ }
1352
+ }
1353
+ else if (claims.nonce !== expectedNonce) {
1354
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1355
+ expected: expectedNonce,
1356
+ claims,
1357
+ claim: 'nonce',
1358
+ });
1189
1359
  }
1190
1360
  return result;
1191
1361
  }
1192
- export async function processAuthorizationCodeOAuth2Response(as, client, response) {
1193
- const result = await processGenericAccessTokenResponse(as, client, response, true);
1194
- if (isOAuth2Error(result)) {
1195
- return result;
1196
- }
1197
- if (result.id_token !== undefined) {
1198
- if (typeof result.id_token === 'string' && result.id_token.length) {
1199
- throw new OPE('Unexpected ID Token returned, use processAuthorizationCodeOpenIDResponse() for OpenID Connect callback processing');
1362
+ async function processAuthorizationCodeOAuth2Response(as, client, response, options) {
1363
+ const result = await processGenericAccessTokenResponse(as, client, response, undefined, options);
1364
+ const claims = getValidatedIdTokenClaims(result);
1365
+ if (claims) {
1366
+ if (client.default_max_age !== undefined) {
1367
+ assertNumber(client.default_max_age, false, '"client.default_max_age"');
1368
+ const now = epochTime() + getClockSkew(client);
1369
+ const tolerance = getClockTolerance(client);
1370
+ if (claims.auth_time + client.default_max_age < now - tolerance) {
1371
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: 'auth_time' });
1372
+ }
1373
+ }
1374
+ if (claims.nonce !== undefined) {
1375
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1376
+ expected: undefined,
1377
+ claims,
1378
+ claim: 'nonce',
1379
+ });
1200
1380
  }
1201
- delete result.id_token;
1202
1381
  }
1203
1382
  return result;
1204
1383
  }
1384
+ export const WWW_AUTHENTICATE_CHALLENGE = 'OAUTH_WWW_AUTHENTICATE_CHALLENGE';
1385
+ export const RESPONSE_BODY_ERROR = 'OAUTH_RESPONSE_BODY_ERROR';
1386
+ export const UNSUPPORTED_OPERATION = 'OAUTH_UNSUPPORTED_OPERATION';
1387
+ export const AUTHORIZATION_RESPONSE_ERROR = 'OAUTH_AUTHORIZATION_RESPONSE_ERROR';
1388
+ export const JWT_USERINFO_EXPECTED = 'OAUTH_JWT_USERINFO_EXPECTED';
1389
+ export const PARSE_ERROR = 'OAUTH_PARSE_ERROR';
1390
+ export const INVALID_RESPONSE = 'OAUTH_INVALID_RESPONSE';
1391
+ export const INVALID_REQUEST = 'OAUTH_INVALID_REQUEST';
1392
+ export const RESPONSE_IS_NOT_JSON = 'OAUTH_RESPONSE_IS_NOT_JSON';
1393
+ export const RESPONSE_IS_NOT_CONFORM = 'OAUTH_RESPONSE_IS_NOT_CONFORM';
1394
+ export const HTTP_REQUEST_FORBIDDEN = 'OAUTH_HTTP_REQUEST_FORBIDDEN';
1395
+ export const REQUEST_PROTOCOL_FORBIDDEN = 'OAUTH_REQUEST_PROTOCOL_FORBIDDEN';
1396
+ export const JWT_TIMESTAMP_CHECK = 'OAUTH_JWT_TIMESTAMP_CHECK_FAILED';
1397
+ export const JWT_CLAIM_COMPARISON = 'OAUTH_JWT_CLAIM_COMPARISON_FAILED';
1398
+ export const JSON_ATTRIBUTE_COMPARISON = 'OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED';
1399
+ export const KEY_SELECTION = 'OAUTH_KEY_SELECTION_FAILED';
1400
+ export const MISSING_SERVER_METADATA = 'OAUTH_MISSING_SERVER_METADATA';
1401
+ export const INVALID_SERVER_METADATA = 'OAUTH_INVALID_SERVER_METADATA';
1205
1402
  function checkJwtType(expected, result) {
1206
1403
  if (typeof result.header.typ !== 'string' || normalizeTyp(result.header.typ) !== expected) {
1207
- throw new OPE('unexpected JWT "typ" header parameter value');
1404
+ throw OPE('unexpected JWT "typ" header parameter value', INVALID_RESPONSE, {
1405
+ header: result.header,
1406
+ });
1208
1407
  }
1209
1408
  return result;
1210
1409
  }
1211
- export async function clientCredentialsGrantRequest(as, client, parameters, options) {
1410
+ export async function clientCredentialsGrantRequest(as, client, clientAuthentication, parameters, options) {
1212
1411
  assertAs(as);
1213
1412
  assertClient(client);
1214
- return tokenEndpointRequest(as, client, 'client_credentials', new URLSearchParams(parameters), options);
1413
+ return tokenEndpointRequest(as, client, clientAuthentication, 'client_credentials', new URLSearchParams(parameters), options);
1215
1414
  }
1216
- export async function genericTokenEndpointRequest(as, client, grantType, parameters, options) {
1415
+ export async function genericTokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
1217
1416
  assertAs(as);
1218
1417
  assertClient(client);
1219
- if (!validateString(grantType)) {
1220
- throw new TypeError('"grantType" must be a non-empty string');
1221
- }
1222
- return tokenEndpointRequest(as, client, grantType, new URLSearchParams(parameters), options);
1418
+ assertString(grantType, '"grantType"');
1419
+ return tokenEndpointRequest(as, client, clientAuthentication, grantType, new URLSearchParams(parameters), options);
1223
1420
  }
1224
- export async function processClientCredentialsResponse(as, client, response) {
1225
- const result = await processGenericAccessTokenResponse(as, client, response, true, true);
1226
- if (isOAuth2Error(result)) {
1227
- return result;
1228
- }
1229
- return result;
1421
+ export async function processGenericTokenEndpointResponse(as, client, response, options) {
1422
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1423
+ }
1424
+ export async function processClientCredentialsResponse(as, client, response, options) {
1425
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1230
1426
  }
1231
- export async function revocationRequest(as, client, token, options) {
1427
+ export async function revocationRequest(as, client, clientAuthentication, token, options) {
1232
1428
  assertAs(as);
1233
1429
  assertClient(client);
1234
- if (!validateString(token)) {
1235
- throw new TypeError('"token" must be a non-empty string');
1236
- }
1237
- const url = resolveEndpoint(as, 'revocation_endpoint', alias(client, options));
1430
+ assertString(token, '"token"');
1431
+ const url = resolveEndpoint(as, 'revocation_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1238
1432
  const body = new URLSearchParams(options?.additionalParameters);
1239
1433
  body.set('token', token);
1240
1434
  const headers = prepareHeaders(options?.headers);
1241
1435
  headers.delete('accept');
1242
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
1436
+ return authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
1243
1437
  }
1244
1438
  export async function processRevocationResponse(response) {
1245
1439
  if (!looseInstanceOf(response, Response)) {
1246
- throw new TypeError('"response" must be an instance of Response');
1440
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1441
+ }
1442
+ let challenges;
1443
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1444
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
1247
1445
  }
1248
1446
  if (response.status !== 200) {
1249
1447
  let err;
1250
1448
  if ((err = await handleOAuthBodyError(response))) {
1251
- return err;
1449
+ await response.body?.cancel();
1450
+ throw new ResponseBodyError('server responded with an error in the response body', {
1451
+ cause: err,
1452
+ response,
1453
+ });
1252
1454
  }
1253
- throw new OPE('"response" is not a conform Revocation Endpoint response');
1455
+ throw OPE('"response" is not a conform Revocation Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1254
1456
  }
1255
1457
  return undefined;
1256
1458
  }
1257
1459
  function assertReadableResponse(response) {
1258
1460
  if (response.bodyUsed) {
1259
- throw new TypeError('"response" body has been used already');
1461
+ throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
1260
1462
  }
1261
1463
  }
1262
- export async function introspectionRequest(as, client, token, options) {
1464
+ export async function introspectionRequest(as, client, clientAuthentication, token, options) {
1263
1465
  assertAs(as);
1264
1466
  assertClient(client);
1265
- if (!validateString(token)) {
1266
- throw new TypeError('"token" must be a non-empty string');
1267
- }
1268
- const url = resolveEndpoint(as, 'introspection_endpoint', alias(client, options));
1467
+ assertString(token, '"token"');
1468
+ const url = resolveEndpoint(as, 'introspection_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1269
1469
  const body = new URLSearchParams(options?.additionalParameters);
1270
1470
  body.set('token', token);
1271
1471
  const headers = prepareHeaders(options?.headers);
@@ -1275,109 +1475,113 @@ export async function introspectionRequest(as, client, token, options) {
1275
1475
  else {
1276
1476
  headers.set('accept', 'application/json');
1277
1477
  }
1278
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
1478
+ return authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
1279
1479
  }
1280
- export async function processIntrospectionResponse(as, client, response) {
1480
+ export async function processIntrospectionResponse(as, client, response, options) {
1281
1481
  assertAs(as);
1282
1482
  assertClient(client);
1283
1483
  if (!looseInstanceOf(response, Response)) {
1284
- throw new TypeError('"response" must be an instance of Response');
1484
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1485
+ }
1486
+ let challenges;
1487
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1488
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
1285
1489
  }
1286
1490
  if (response.status !== 200) {
1287
1491
  let err;
1288
1492
  if ((err = await handleOAuthBodyError(response))) {
1289
- return err;
1493
+ await response.body?.cancel();
1494
+ throw new ResponseBodyError('server responded with an error in the response body', {
1495
+ cause: err,
1496
+ response,
1497
+ });
1290
1498
  }
1291
- throw new OPE('"response" is not a conform Introspection Endpoint response');
1499
+ throw OPE('"response" is not a conform Introspection Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1292
1500
  }
1293
1501
  let json;
1294
1502
  if (getContentType(response) === 'application/token-introspection+jwt') {
1295
1503
  assertReadableResponse(response);
1296
- const { claims, jwt } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
1504
+ const { claims, jwt } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported, 'RS256'), getClockSkew(client), getClockTolerance(client), options?.[jweDecrypt])
1297
1505
  .then(checkJwtType.bind(undefined, 'token-introspection+jwt'))
1298
1506
  .then(validatePresence.bind(undefined, ['aud', 'iat', 'iss']))
1299
- .then(validateIssuer.bind(undefined, as.issuer))
1507
+ .then(validateIssuer.bind(undefined, as))
1300
1508
  .then(validateAudience.bind(undefined, client.client_id));
1301
- jwtResponseBodies.set(response, jwt);
1509
+ jwtRefs.set(response, jwt);
1302
1510
  json = claims.token_introspection;
1303
1511
  if (!isJsonObject(json)) {
1304
- throw new OPE('JWT "token_introspection" claim must be a JSON object');
1512
+ throw OPE('JWT "token_introspection" claim must be a JSON object', INVALID_RESPONSE, {
1513
+ claims,
1514
+ });
1305
1515
  }
1306
1516
  }
1307
1517
  else {
1308
1518
  assertReadableResponse(response);
1519
+ assertApplicationJson(response);
1309
1520
  try {
1310
1521
  json = await response.json();
1311
1522
  }
1312
1523
  catch (cause) {
1313
- throw new OPE('failed to parse "response" body as JSON', { cause });
1524
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1314
1525
  }
1315
1526
  if (!isJsonObject(json)) {
1316
- throw new OPE('"response" body must be a top level object');
1527
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1317
1528
  }
1318
1529
  }
1319
1530
  if (typeof json.active !== 'boolean') {
1320
- throw new OPE('"response" body "active" property must be a boolean');
1531
+ throw OPE('"response" body "active" property must be a boolean', INVALID_RESPONSE, {
1532
+ body: json,
1533
+ });
1321
1534
  }
1322
1535
  return json;
1323
1536
  }
1324
1537
  async function jwksRequest(as, options) {
1325
1538
  assertAs(as);
1326
- const url = resolveEndpoint(as, 'jwks_uri');
1539
+ const url = resolveEndpoint(as, 'jwks_uri', false, options?.[allowInsecureRequests] !== true);
1327
1540
  const headers = prepareHeaders(options?.headers);
1328
1541
  headers.set('accept', 'application/json');
1329
1542
  headers.append('accept', 'application/jwk-set+json');
1330
1543
  return (options?.[customFetch] || fetch)(url.href, {
1544
+ body: undefined,
1331
1545
  headers: Object.fromEntries(headers.entries()),
1332
1546
  method: 'GET',
1333
1547
  redirect: 'manual',
1334
- signal: options?.signal ? signal(options.signal) : null,
1335
- }).then(processDpopNonce);
1548
+ signal: options?.signal ? signal(options.signal) : undefined,
1549
+ });
1336
1550
  }
1337
1551
  async function processJwksResponse(response) {
1338
1552
  if (!looseInstanceOf(response, Response)) {
1339
- throw new TypeError('"response" must be an instance of Response');
1553
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1340
1554
  }
1341
1555
  if (response.status !== 200) {
1342
- throw new OPE('"response" is not a conform JSON Web Key Set response');
1556
+ throw OPE('"response" is not a conform JSON Web Key Set response', RESPONSE_IS_NOT_CONFORM, response);
1343
1557
  }
1344
1558
  assertReadableResponse(response);
1559
+ assertContentTypes(response, 'application/json', 'application/jwk-set+json');
1345
1560
  let json;
1346
1561
  try {
1347
1562
  json = await response.json();
1348
1563
  }
1349
1564
  catch (cause) {
1350
- throw new OPE('failed to parse "response" body as JSON', { cause });
1565
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1351
1566
  }
1352
1567
  if (!isJsonObject(json)) {
1353
- throw new OPE('"response" body must be a top level object');
1568
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1354
1569
  }
1355
1570
  if (!Array.isArray(json.keys)) {
1356
- throw new OPE('"response" body "keys" property must be an array');
1571
+ throw OPE('"response" body "keys" property must be an array', INVALID_RESPONSE, { body: json });
1357
1572
  }
1358
1573
  if (!Array.prototype.every.call(json.keys, isJsonObject)) {
1359
- throw new OPE('"response" body "keys" property members must be JWK formatted objects');
1574
+ throw OPE('"response" body "keys" property members must be JWK formatted objects', INVALID_RESPONSE, { body: json });
1360
1575
  }
1361
1576
  return json;
1362
1577
  }
1363
1578
  async function handleOAuthBodyError(response) {
1364
1579
  if (response.status > 399 && response.status < 500) {
1365
1580
  assertReadableResponse(response);
1581
+ assertApplicationJson(response);
1366
1582
  try {
1367
- const json = await response.json();
1583
+ const json = await response.clone().json();
1368
1584
  if (isJsonObject(json) && typeof json.error === 'string' && json.error.length) {
1369
- if (json.error_description !== undefined && typeof json.error_description !== 'string') {
1370
- delete json.error_description;
1371
- }
1372
- if (json.error_uri !== undefined && typeof json.error_uri !== 'string') {
1373
- delete json.error_uri;
1374
- }
1375
- if (json.algs !== undefined && typeof json.algs !== 'string') {
1376
- delete json.algs;
1377
- }
1378
- if (json.scope !== undefined && typeof json.scope !== 'string') {
1379
- delete json.scope;
1380
- }
1381
1585
  return json;
1382
1586
  }
1383
1587
  }
@@ -1385,19 +1589,42 @@ async function handleOAuthBodyError(response) {
1385
1589
  }
1386
1590
  return undefined;
1387
1591
  }
1388
- function checkSupportedJwsAlg(alg) {
1389
- if (!SUPPORTED_JWS_ALGS.includes(alg)) {
1390
- throw new UnsupportedOperationError('unsupported JWS "alg" identifier');
1592
+ function supported(alg) {
1593
+ switch (alg) {
1594
+ case 'PS256':
1595
+ case 'ES256':
1596
+ case 'RS256':
1597
+ case 'PS384':
1598
+ case 'ES384':
1599
+ case 'RS384':
1600
+ case 'PS512':
1601
+ case 'ES512':
1602
+ case 'RS512':
1603
+ case 'Ed25519':
1604
+ case 'EdDSA':
1605
+ return true;
1606
+ default:
1607
+ return false;
1608
+ }
1609
+ }
1610
+ function checkSupportedJwsAlg(header) {
1611
+ if (!supported(header.alg)) {
1612
+ throw new UnsupportedOperationError('unsupported JWS "alg" identifier', {
1613
+ cause: { alg: header.alg },
1614
+ });
1391
1615
  }
1392
- return alg;
1393
1616
  }
1394
- function checkRsaKeyAlgorithm(algorithm) {
1617
+ function checkRsaKeyAlgorithm(key) {
1618
+ const { algorithm } = key;
1395
1619
  if (typeof algorithm.modulusLength !== 'number' || algorithm.modulusLength < 2048) {
1396
- throw new OPE(`${algorithm.name} modulusLength must be at least 2048 bits`);
1620
+ throw new UnsupportedOperationError(`unsupported ${algorithm.name} modulusLength`, {
1621
+ cause: key,
1622
+ });
1397
1623
  }
1398
1624
  }
1399
- function ecdsaHashName(namedCurve) {
1400
- switch (namedCurve) {
1625
+ function ecdsaHashName(key) {
1626
+ const { algorithm } = key;
1627
+ switch (algorithm.namedCurve) {
1401
1628
  case 'P-256':
1402
1629
  return 'SHA-256';
1403
1630
  case 'P-384':
@@ -1405,7 +1632,7 @@ function ecdsaHashName(namedCurve) {
1405
1632
  case 'P-521':
1406
1633
  return 'SHA-512';
1407
1634
  default:
1408
- throw new UnsupportedOperationError();
1635
+ throw new UnsupportedOperationError('unsupported ECDSA namedCurve', { cause: key });
1409
1636
  }
1410
1637
  }
1411
1638
  function keyToSubtle(key) {
@@ -1413,10 +1640,10 @@ function keyToSubtle(key) {
1413
1640
  case 'ECDSA':
1414
1641
  return {
1415
1642
  name: key.algorithm.name,
1416
- hash: ecdsaHashName(key.algorithm.namedCurve),
1643
+ hash: ecdsaHashName(key),
1417
1644
  };
1418
1645
  case 'RSA-PSS': {
1419
- checkRsaKeyAlgorithm(key.algorithm);
1646
+ checkRsaKeyAlgorithm(key);
1420
1647
  switch (key.algorithm.hash.name) {
1421
1648
  case 'SHA-256':
1422
1649
  case 'SHA-384':
@@ -1426,103 +1653,109 @@ function keyToSubtle(key) {
1426
1653
  saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3,
1427
1654
  };
1428
1655
  default:
1429
- throw new UnsupportedOperationError();
1656
+ throw new UnsupportedOperationError('unsupported RSA-PSS hash name', { cause: key });
1430
1657
  }
1431
1658
  }
1432
1659
  case 'RSASSA-PKCS1-v1_5':
1433
- checkRsaKeyAlgorithm(key.algorithm);
1660
+ checkRsaKeyAlgorithm(key);
1434
1661
  return key.algorithm.name;
1435
- case 'Ed448':
1436
1662
  case 'Ed25519':
1663
+ case 'EdDSA':
1437
1664
  return key.algorithm.name;
1438
1665
  }
1439
- throw new UnsupportedOperationError();
1666
+ throw new UnsupportedOperationError('unsupported CryptoKey algorithm name', { cause: key });
1440
1667
  }
1441
- const noSignatureCheck = Symbol();
1442
1668
  async function validateJwsSignature(protectedHeader, payload, key, signature) {
1443
- const input = `${protectedHeader}.${payload}`;
1444
- const verified = await crypto.subtle.verify(keyToSubtle(key), key, signature, buf(input));
1669
+ const data = buf(`${protectedHeader}.${payload}`);
1670
+ const algorithm = keyToSubtle(key);
1671
+ const verified = await crypto.subtle.verify(algorithm, key, signature, data);
1445
1672
  if (!verified) {
1446
- throw new OPE('JWT signature verification failed');
1673
+ throw OPE('JWT signature verification failed', INVALID_RESPONSE, {
1674
+ key,
1675
+ data,
1676
+ signature,
1677
+ algorithm,
1678
+ });
1447
1679
  }
1448
1680
  }
1449
- async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance, decryptJwt) {
1450
- let { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.');
1681
+ async function validateJwt(jws, checkAlg, clockSkew, clockTolerance, decryptJwt) {
1682
+ let { 0: protectedHeader, 1: payload, length } = jws.split('.');
1451
1683
  if (length === 5) {
1452
1684
  if (decryptJwt !== undefined) {
1453
1685
  jws = await decryptJwt(jws);
1454
- ({ 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.'));
1686
+ ({ 0: protectedHeader, 1: payload, length } = jws.split('.'));
1455
1687
  }
1456
1688
  else {
1457
- throw new UnsupportedOperationError('JWE structure JWTs are not supported');
1689
+ throw new UnsupportedOperationError('JWE decryption is not configured', { cause: jws });
1458
1690
  }
1459
1691
  }
1460
1692
  if (length !== 3) {
1461
- throw new OPE('Invalid JWT');
1693
+ throw OPE('Invalid JWT', INVALID_RESPONSE, jws);
1462
1694
  }
1463
1695
  let header;
1464
1696
  try {
1465
1697
  header = JSON.parse(buf(b64u(protectedHeader)));
1466
1698
  }
1467
1699
  catch (cause) {
1468
- throw new OPE('failed to parse JWT Header body as base64url encoded JSON', { cause });
1700
+ throw OPE('failed to parse JWT Header body as base64url encoded JSON', PARSE_ERROR, cause);
1469
1701
  }
1470
1702
  if (!isJsonObject(header)) {
1471
- throw new OPE('JWT Header must be a top level object');
1703
+ throw OPE('JWT Header must be a top level object', INVALID_RESPONSE, jws);
1472
1704
  }
1473
1705
  checkAlg(header);
1474
1706
  if (header.crit !== undefined) {
1475
- throw new OPE('unexpected JWT "crit" header parameter');
1476
- }
1477
- const signature = b64u(encodedSignature);
1478
- let key;
1479
- if (getKey !== noSignatureCheck) {
1480
- key = await getKey(header);
1481
- await validateJwsSignature(protectedHeader, payload, key, signature);
1707
+ throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
1708
+ cause: { header },
1709
+ });
1482
1710
  }
1483
1711
  let claims;
1484
1712
  try {
1485
1713
  claims = JSON.parse(buf(b64u(payload)));
1486
1714
  }
1487
1715
  catch (cause) {
1488
- throw new OPE('failed to parse JWT Payload body as base64url encoded JSON', { cause });
1716
+ throw OPE('failed to parse JWT Payload body as base64url encoded JSON', PARSE_ERROR, cause);
1489
1717
  }
1490
1718
  if (!isJsonObject(claims)) {
1491
- throw new OPE('JWT Payload must be a top level object');
1719
+ throw OPE('JWT Payload must be a top level object', INVALID_RESPONSE, jws);
1492
1720
  }
1493
1721
  const now = epochTime() + clockSkew;
1494
1722
  if (claims.exp !== undefined) {
1495
1723
  if (typeof claims.exp !== 'number') {
1496
- throw new OPE('unexpected JWT "exp" (expiration time) claim type');
1724
+ throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, { claims });
1497
1725
  }
1498
1726
  if (claims.exp <= now - clockTolerance) {
1499
- throw new OPE('unexpected JWT "exp" (expiration time) claim value, timestamp is <= now()');
1727
+ throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, { claims, now, tolerance: clockTolerance, claim: 'exp' });
1500
1728
  }
1501
1729
  }
1502
1730
  if (claims.iat !== undefined) {
1503
1731
  if (typeof claims.iat !== 'number') {
1504
- throw new OPE('unexpected JWT "iat" (issued at) claim type');
1732
+ throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, { claims });
1505
1733
  }
1506
1734
  }
1507
1735
  if (claims.iss !== undefined) {
1508
1736
  if (typeof claims.iss !== 'string') {
1509
- throw new OPE('unexpected JWT "iss" (issuer) claim type');
1737
+ throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, { claims });
1510
1738
  }
1511
1739
  }
1512
1740
  if (claims.nbf !== undefined) {
1513
1741
  if (typeof claims.nbf !== 'number') {
1514
- throw new OPE('unexpected JWT "nbf" (not before) claim type');
1742
+ throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, { claims });
1515
1743
  }
1516
1744
  if (claims.nbf > now + clockTolerance) {
1517
- throw new OPE('unexpected JWT "nbf" (not before) claim value, timestamp is > now()');
1745
+ throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
1746
+ claims,
1747
+ now,
1748
+ tolerance: clockTolerance,
1749
+ claim: 'nbf',
1750
+ });
1518
1751
  }
1519
1752
  }
1520
1753
  if (claims.aud !== undefined) {
1521
1754
  if (typeof claims.aud !== 'string' && !Array.isArray(claims.aud)) {
1522
- throw new OPE('unexpected JWT "aud" (audience) claim type');
1755
+ throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, { claims });
1523
1756
  }
1524
1757
  }
1525
- return { header, claims, signature, key, jwt: jws };
1758
+ return { header, claims, jwt: jws };
1526
1759
  }
1527
1760
  export async function validateJwtAuthResponse(as, client, parameters, expectedState, options) {
1528
1761
  assertAs(as);
@@ -1531,16 +1764,20 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
1531
1764
  parameters = parameters.searchParams;
1532
1765
  }
1533
1766
  if (!(parameters instanceof URLSearchParams)) {
1534
- throw new TypeError('"parameters" must be an instance of URLSearchParams, or URL');
1767
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
1535
1768
  }
1536
1769
  const response = getURLSearchParameter(parameters, 'response');
1537
1770
  if (!response) {
1538
- throw new OPE('"parameters" does not contain a JARM response');
1771
+ throw OPE('"parameters" does not contain a JARM response', INVALID_RESPONSE);
1539
1772
  }
1540
- const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
1773
+ const { claims, header, jwt } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported, 'RS256'), getClockSkew(client), getClockTolerance(client), options?.[jweDecrypt])
1541
1774
  .then(validatePresence.bind(undefined, ['aud', 'exp', 'iss']))
1542
- .then(validateIssuer.bind(undefined, as.issuer))
1775
+ .then(validateIssuer.bind(undefined, as))
1543
1776
  .then(validateAudience.bind(undefined, client.client_id));
1777
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwt.split('.');
1778
+ const signature = b64u(encodedSignature);
1779
+ const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
1780
+ await validateJwsSignature(protectedHeader, payload, key, signature);
1544
1781
  const result = new URLSearchParams();
1545
1782
  for (const [key, value] of Object.entries(claims)) {
1546
1783
  if (typeof value === 'string' && key !== 'aud') {
@@ -1549,9 +1786,9 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
1549
1786
  }
1550
1787
  return validateAuthResponse(as, client, result, expectedState);
1551
1788
  }
1552
- async function idTokenHash(alg, data, key) {
1789
+ async function idTokenHash(data, header, claimName) {
1553
1790
  let algorithm;
1554
- switch (alg) {
1791
+ switch (header.alg) {
1555
1792
  case 'RS256':
1556
1793
  case 'PS256':
1557
1794
  case 'ES256':
@@ -1565,35 +1802,37 @@ async function idTokenHash(alg, data, key) {
1565
1802
  case 'RS512':
1566
1803
  case 'PS512':
1567
1804
  case 'ES512':
1805
+ case 'Ed25519':
1806
+ case 'EdDSA':
1568
1807
  algorithm = 'SHA-512';
1569
1808
  break;
1570
- case 'EdDSA':
1571
- if (key.algorithm.name === 'Ed25519') {
1572
- algorithm = 'SHA-512';
1573
- break;
1574
- }
1575
- throw new UnsupportedOperationError();
1576
1809
  default:
1577
- throw new UnsupportedOperationError();
1810
+ throw new UnsupportedOperationError(`unsupported JWS algorithm for ${claimName} calculation`, { cause: { alg: header.alg } });
1578
1811
  }
1579
1812
  const digest = await crypto.subtle.digest(algorithm, buf(data));
1580
1813
  return b64u(digest.slice(0, digest.byteLength / 2));
1581
1814
  }
1582
- async function idTokenHashMatches(data, actual, alg, key) {
1583
- const expected = await idTokenHash(alg, data, key);
1815
+ async function idTokenHashMatches(data, actual, header, claimName) {
1816
+ const expected = await idTokenHash(data, header, claimName);
1584
1817
  return actual === expected;
1585
1818
  }
1586
1819
  export async function validateDetachedSignatureResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options) {
1820
+ return validateHybridResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options, true);
1821
+ }
1822
+ export async function validateCodeIdTokenResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options) {
1823
+ return validateHybridResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options, false);
1824
+ }
1825
+ async function validateHybridResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options, fapi) {
1587
1826
  assertAs(as);
1588
1827
  assertClient(client);
1589
1828
  if (parameters instanceof URL) {
1590
1829
  if (!parameters.hash.length) {
1591
- throw new TypeError('"parameters" as an instance of URL must contain a hash (fragment) with the Authorization Response parameters');
1830
+ throw CodedTypeError('"parameters" as an instance of URL must contain a hash (fragment) with the Authorization Response parameters', ERR_INVALID_ARG_VALUE);
1592
1831
  }
1593
1832
  parameters = new URLSearchParams(parameters.hash.slice(1));
1594
1833
  }
1595
1834
  if (!(parameters instanceof URLSearchParams)) {
1596
- throw new TypeError('"parameters" must be an instance of URLSearchParams');
1835
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams', ERR_INVALID_ARG_TYPE);
1597
1836
  }
1598
1837
  parameters = new URLSearchParams(parameters);
1599
1838
  const id_token = getURLSearchParameter(parameters, 'id_token');
@@ -1603,23 +1842,18 @@ export async function validateDetachedSignatureResponse(as, client, parameters,
1603
1842
  case expectNoState:
1604
1843
  break;
1605
1844
  default:
1606
- if (!validateString(expectedState)) {
1607
- throw new TypeError('"expectedState" must be a non-empty string');
1608
- }
1845
+ assertString(expectedState, '"expectedState" argument');
1609
1846
  }
1610
1847
  const result = validateAuthResponse({
1611
1848
  ...as,
1612
1849
  authorization_response_iss_parameter_supported: false,
1613
1850
  }, client, parameters, expectedState);
1614
- if (isOAuth2Error(result)) {
1615
- return result;
1616
- }
1617
1851
  if (!id_token) {
1618
- throw new OPE('"parameters" does not contain an ID Token');
1852
+ throw OPE('"parameters" does not contain an ID Token', INVALID_RESPONSE);
1619
1853
  }
1620
1854
  const code = getURLSearchParameter(parameters, 'code');
1621
1855
  if (!code) {
1622
- throw new OPE('"parameters" does not contain an Authorization Code');
1856
+ throw OPE('"parameters" does not contain an Authorization Code', INVALID_RESPONSE);
1623
1857
  }
1624
1858
  const requiredClaims = [
1625
1859
  'aud',
@@ -1630,86 +1864,132 @@ export async function validateDetachedSignatureResponse(as, client, parameters,
1630
1864
  'nonce',
1631
1865
  'c_hash',
1632
1866
  ];
1633
- if (typeof expectedState === 'string') {
1867
+ const state = parameters.get('state');
1868
+ if (fapi && (typeof expectedState === 'string' || state !== null)) {
1634
1869
  requiredClaims.push('s_hash');
1635
1870
  }
1636
- const { claims, header, key } = await validateJwt(id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
1871
+ if (maxAge !== undefined) {
1872
+ assertNumber(maxAge, false, '"maxAge" argument');
1873
+ }
1874
+ else if (client.default_max_age !== undefined) {
1875
+ assertNumber(client.default_max_age, false, '"client.default_max_age"');
1876
+ }
1877
+ maxAge ??= client.default_max_age ?? skipAuthTimeCheck;
1878
+ if (client.require_auth_time || maxAge !== skipAuthTimeCheck) {
1879
+ requiredClaims.push('auth_time');
1880
+ }
1881
+ const { claims, header, jwt } = await validateJwt(id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, 'RS256'), getClockSkew(client), getClockTolerance(client), options?.[jweDecrypt])
1637
1882
  .then(validatePresence.bind(undefined, requiredClaims))
1638
- .then(validateIssuer.bind(undefined, as.issuer))
1883
+ .then(validateIssuer.bind(undefined, as))
1639
1884
  .then(validateAudience.bind(undefined, client.client_id));
1640
1885
  const clockSkew = getClockSkew(client);
1641
1886
  const now = epochTime() + clockSkew;
1642
1887
  if (claims.iat < now - 3600) {
1643
- throw new OPE('unexpected JWT "iat" (issued at) claim value, it is too far in the past');
1888
+ throw OPE('unexpected JWT "iat" (issued at) claim value, it is too far in the past', JWT_TIMESTAMP_CHECK, { now, claims, claim: 'iat' });
1644
1889
  }
1645
- if (typeof claims.c_hash !== 'string' ||
1646
- (await idTokenHashMatches(code, claims.c_hash, header.alg, key)) !== true) {
1647
- throw new OPE('invalid ID Token "c_hash" (code hash) claim value');
1648
- }
1649
- if (claims.s_hash !== undefined && typeof expectedState !== 'string') {
1650
- throw new OPE('could not verify ID Token "s_hash" (state hash) claim value');
1651
- }
1652
- if (typeof expectedState === 'string' &&
1653
- (typeof claims.s_hash !== 'string' ||
1654
- (await idTokenHashMatches(expectedState, claims.s_hash, header.alg, key)) !== true)) {
1655
- throw new OPE('invalid ID Token "s_hash" (state hash) claim value');
1656
- }
1657
- if (claims.auth_time !== undefined &&
1658
- (!Number.isFinite(claims.auth_time) || Math.sign(claims.auth_time) !== 1)) {
1659
- throw new OPE('ID Token "auth_time" (authentication time) must be a positive number');
1660
- }
1661
- maxAge ?? (maxAge = client.default_max_age ?? skipAuthTimeCheck);
1662
- if ((client.require_auth_time || maxAge !== skipAuthTimeCheck) &&
1663
- claims.auth_time === undefined) {
1664
- throw new OPE('ID Token "auth_time" (authentication time) claim missing');
1890
+ assertString(claims.c_hash, 'ID Token "c_hash" (code hash) claim value', INVALID_RESPONSE, {
1891
+ claims,
1892
+ });
1893
+ if (claims.auth_time !== undefined) {
1894
+ assertNumber(claims.auth_time, false, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
1665
1895
  }
1666
1896
  if (maxAge !== skipAuthTimeCheck) {
1667
- if (typeof maxAge !== 'number' || maxAge < 0) {
1668
- throw new TypeError('"maxAge" must be a non-negative number');
1669
- }
1670
1897
  const now = epochTime() + getClockSkew(client);
1671
1898
  const tolerance = getClockTolerance(client);
1672
1899
  if (claims.auth_time + maxAge < now - tolerance) {
1673
- throw new OPE('too much time has elapsed since the last End-User authentication');
1900
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: 'auth_time' });
1674
1901
  }
1675
1902
  }
1676
- if (!validateString(expectedNonce)) {
1677
- throw new TypeError('"expectedNonce" must be a non-empty string');
1678
- }
1903
+ assertString(expectedNonce, '"expectedNonce" argument');
1679
1904
  if (claims.nonce !== expectedNonce) {
1680
- throw new OPE('unexpected ID Token "nonce" claim value');
1905
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1906
+ expected: expectedNonce,
1907
+ claims,
1908
+ claim: 'nonce',
1909
+ });
1681
1910
  }
1682
1911
  if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
1683
1912
  if (claims.azp === undefined) {
1684
- throw new OPE('ID Token "aud" (audience) claim includes additional untrusted audiences');
1913
+ throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: 'aud' });
1685
1914
  }
1686
1915
  if (claims.azp !== client.client_id) {
1687
- throw new OPE('unexpected ID Token "azp" (authorized party) claim value');
1916
+ throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
1917
+ expected: client.client_id,
1918
+ claims,
1919
+ claim: 'azp',
1920
+ });
1921
+ }
1922
+ }
1923
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwt.split('.');
1924
+ const signature = b64u(encodedSignature);
1925
+ const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
1926
+ await validateJwsSignature(protectedHeader, payload, key, signature);
1927
+ if ((await idTokenHashMatches(code, claims.c_hash, header, 'c_hash')) !== true) {
1928
+ throw OPE('invalid ID Token "c_hash" (code hash) claim value', JWT_CLAIM_COMPARISON, {
1929
+ code,
1930
+ alg: header.alg,
1931
+ claim: 'c_hash',
1932
+ claims,
1933
+ });
1934
+ }
1935
+ if ((fapi && state !== null) || claims.s_hash !== undefined) {
1936
+ assertString(claims.s_hash, 'ID Token "s_hash" (state hash) claim value', INVALID_RESPONSE, {
1937
+ claims,
1938
+ });
1939
+ assertString(state, '"state" response parameter', INVALID_RESPONSE, { parameters });
1940
+ if ((await idTokenHashMatches(state, claims.s_hash, header, 's_hash')) !== true) {
1941
+ throw OPE('invalid ID Token "s_hash" (state hash) claim value', JWT_CLAIM_COMPARISON, {
1942
+ state,
1943
+ alg: header.alg,
1944
+ claim: 's_hash',
1945
+ claims,
1946
+ });
1688
1947
  }
1689
1948
  }
1690
1949
  return result;
1691
1950
  }
1692
- function checkSigningAlgorithm(client, issuer, header) {
1951
+ const SUPPORTED_JWS_ALGS = Symbol();
1952
+ function checkSigningAlgorithm(client, issuer, fallback, header) {
1693
1953
  if (client !== undefined) {
1694
- if (header.alg !== client) {
1695
- throw new OPE('unexpected JWT "alg" header parameter');
1954
+ if (typeof client === 'string' ? header.alg !== client : !client.includes(header.alg)) {
1955
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1956
+ header,
1957
+ expected: client,
1958
+ reason: 'client configuration',
1959
+ });
1696
1960
  }
1697
1961
  return;
1698
1962
  }
1699
1963
  if (Array.isArray(issuer)) {
1700
1964
  if (!issuer.includes(header.alg)) {
1701
- throw new OPE('unexpected JWT "alg" header parameter');
1965
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1966
+ header,
1967
+ expected: issuer,
1968
+ reason: 'authorization server metadata',
1969
+ });
1702
1970
  }
1703
1971
  return;
1704
1972
  }
1705
- if (header.alg !== 'RS256') {
1706
- throw new OPE('unexpected JWT "alg" header parameter');
1973
+ if (fallback !== undefined) {
1974
+ if (typeof fallback === 'string'
1975
+ ? header.alg !== fallback
1976
+ : typeof fallback === 'symbol'
1977
+ ? !supported(header.alg)
1978
+ : !fallback.includes(header.alg)) {
1979
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1980
+ header,
1981
+ expected: fallback,
1982
+ reason: 'default value',
1983
+ });
1984
+ }
1985
+ return;
1707
1986
  }
1987
+ throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', undefined, { client, issuer, fallback });
1708
1988
  }
1709
1989
  function getURLSearchParameter(parameters, name) {
1710
1990
  const { 0: value, length } = parameters.getAll(name);
1711
1991
  if (length > 1) {
1712
- throw new OPE(`"${name}" parameter must be provided only once`);
1992
+ throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
1713
1993
  }
1714
1994
  return value;
1715
1995
  }
@@ -1722,46 +2002,47 @@ export function validateAuthResponse(as, client, parameters, expectedState) {
1722
2002
  parameters = parameters.searchParams;
1723
2003
  }
1724
2004
  if (!(parameters instanceof URLSearchParams)) {
1725
- throw new TypeError('"parameters" must be an instance of URLSearchParams, or URL');
2005
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
1726
2006
  }
1727
2007
  if (getURLSearchParameter(parameters, 'response')) {
1728
- throw new OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()');
2008
+ throw OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()', INVALID_RESPONSE, { parameters });
1729
2009
  }
1730
2010
  const iss = getURLSearchParameter(parameters, 'iss');
1731
2011
  const state = getURLSearchParameter(parameters, 'state');
1732
2012
  if (!iss && as.authorization_response_iss_parameter_supported) {
1733
- throw new OPE('response parameter "iss" (issuer) missing');
2013
+ throw OPE('response parameter "iss" (issuer) missing', INVALID_RESPONSE, { parameters });
1734
2014
  }
1735
2015
  if (iss && iss !== as.issuer) {
1736
- throw new OPE('unexpected "iss" (issuer) response parameter value');
2016
+ throw OPE('unexpected "iss" (issuer) response parameter value', INVALID_RESPONSE, {
2017
+ expected: as.issuer,
2018
+ parameters,
2019
+ });
1737
2020
  }
1738
2021
  switch (expectedState) {
1739
2022
  case undefined:
1740
2023
  case expectNoState:
1741
2024
  if (state !== undefined) {
1742
- throw new OPE('unexpected "state" response parameter encountered');
2025
+ throw OPE('unexpected "state" response parameter encountered', INVALID_RESPONSE, {
2026
+ expected: undefined,
2027
+ parameters,
2028
+ });
1743
2029
  }
1744
2030
  break;
1745
2031
  case skipStateCheck:
1746
2032
  break;
1747
2033
  default:
1748
- if (!validateString(expectedState)) {
1749
- throw new OPE('"expectedState" must be a non-empty string');
1750
- }
1751
- if (state === undefined) {
1752
- throw new OPE('response parameter "state" missing');
1753
- }
2034
+ assertString(expectedState, '"expectedState" argument');
1754
2035
  if (state !== expectedState) {
1755
- throw new OPE('unexpected "state" response parameter value');
2036
+ throw OPE(state === undefined
2037
+ ? 'response parameter "state" missing'
2038
+ : 'unexpected "state" response parameter value', INVALID_RESPONSE, { expected: expectedState, parameters });
1756
2039
  }
1757
2040
  }
1758
2041
  const error = getURLSearchParameter(parameters, 'error');
1759
2042
  if (error) {
1760
- return {
1761
- error,
1762
- error_description: getURLSearchParameter(parameters, 'error_description'),
1763
- error_uri: getURLSearchParameter(parameters, 'error_uri'),
1764
- };
2043
+ throw new AuthorizationResponseError('authorization response from the server is an error', {
2044
+ cause: parameters,
2045
+ });
1765
2046
  }
1766
2047
  const id_token = getURLSearchParameter(parameters, 'id_token');
1767
2048
  const token = getURLSearchParameter(parameters, 'token');
@@ -1770,7 +2051,7 @@ export function validateAuthResponse(as, client, parameters, expectedState) {
1770
2051
  }
1771
2052
  return brand(new URLSearchParams(parameters));
1772
2053
  }
1773
- function algToSubtle(alg, crv) {
2054
+ function algToSubtle(alg) {
1774
2055
  switch (alg) {
1775
2056
  case 'PS256':
1776
2057
  case 'PS384':
@@ -1785,96 +2066,96 @@ function algToSubtle(alg, crv) {
1785
2066
  return { name: 'ECDSA', namedCurve: `P-${alg.slice(-3)}` };
1786
2067
  case 'ES512':
1787
2068
  return { name: 'ECDSA', namedCurve: 'P-521' };
1788
- case 'EdDSA': {
1789
- switch (crv) {
1790
- case 'Ed25519':
1791
- case 'Ed448':
1792
- return crv;
1793
- default:
1794
- throw new UnsupportedOperationError();
1795
- }
1796
- }
2069
+ case 'Ed25519':
2070
+ case 'EdDSA':
2071
+ return 'Ed25519';
1797
2072
  default:
1798
- throw new UnsupportedOperationError();
2073
+ throw new UnsupportedOperationError('unsupported JWS algorithm', { cause: { alg } });
1799
2074
  }
1800
2075
  }
1801
2076
  async function importJwk(alg, jwk) {
1802
2077
  const { ext, key_ops, use, ...key } = jwk;
1803
- return crypto.subtle.importKey('jwk', key, algToSubtle(alg, jwk.crv), true, ['verify']);
2078
+ return crypto.subtle.importKey('jwk', key, algToSubtle(alg), true, ['verify']);
1804
2079
  }
1805
- export async function deviceAuthorizationRequest(as, client, parameters, options) {
2080
+ export async function deviceAuthorizationRequest(as, client, clientAuthentication, parameters, options) {
1806
2081
  assertAs(as);
1807
2082
  assertClient(client);
1808
- const url = resolveEndpoint(as, 'device_authorization_endpoint', alias(client, options));
2083
+ const url = resolveEndpoint(as, 'device_authorization_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1809
2084
  const body = new URLSearchParams(parameters);
1810
2085
  body.set('client_id', client.client_id);
1811
2086
  const headers = prepareHeaders(options?.headers);
1812
2087
  headers.set('accept', 'application/json');
1813
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
2088
+ return authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
1814
2089
  }
1815
2090
  export async function processDeviceAuthorizationResponse(as, client, response) {
1816
2091
  assertAs(as);
1817
2092
  assertClient(client);
1818
2093
  if (!looseInstanceOf(response, Response)) {
1819
- throw new TypeError('"response" must be an instance of Response');
2094
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
2095
+ }
2096
+ let challenges;
2097
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
2098
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
1820
2099
  }
1821
2100
  if (response.status !== 200) {
1822
2101
  let err;
1823
2102
  if ((err = await handleOAuthBodyError(response))) {
1824
- return err;
2103
+ await response.body?.cancel();
2104
+ throw new ResponseBodyError('server responded with an error in the response body', {
2105
+ cause: err,
2106
+ response,
2107
+ });
1825
2108
  }
1826
- throw new OPE('"response" is not a conform Device Authorization Endpoint response');
2109
+ throw OPE('"response" is not a conform Device Authorization Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1827
2110
  }
1828
2111
  assertReadableResponse(response);
2112
+ assertApplicationJson(response);
1829
2113
  let json;
1830
2114
  try {
1831
2115
  json = await response.json();
1832
2116
  }
1833
2117
  catch (cause) {
1834
- throw new OPE('failed to parse "response" body as JSON', { cause });
2118
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1835
2119
  }
1836
2120
  if (!isJsonObject(json)) {
1837
- throw new OPE('"response" body must be a top level object');
1838
- }
1839
- if (!validateString(json.device_code)) {
1840
- throw new OPE('"response" body "device_code" property must be a non-empty string');
1841
- }
1842
- if (!validateString(json.user_code)) {
1843
- throw new OPE('"response" body "user_code" property must be a non-empty string');
2121
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1844
2122
  }
1845
- if (!validateString(json.verification_uri)) {
1846
- throw new OPE('"response" body "verification_uri" property must be a non-empty string');
1847
- }
1848
- if (typeof json.expires_in !== 'number' || json.expires_in <= 0) {
1849
- throw new OPE('"response" body "expires_in" property must be a positive number');
1850
- }
1851
- if (json.verification_uri_complete !== undefined &&
1852
- !validateString(json.verification_uri_complete)) {
1853
- throw new OPE('"response" body "verification_uri_complete" property must be a non-empty string');
2123
+ assertString(json.device_code, '"response" body "device_code" property', INVALID_RESPONSE, {
2124
+ body: json,
2125
+ });
2126
+ assertString(json.user_code, '"response" body "user_code" property', INVALID_RESPONSE, {
2127
+ body: json,
2128
+ });
2129
+ assertString(json.verification_uri, '"response" body "verification_uri" property', INVALID_RESPONSE, { body: json });
2130
+ let expiresIn = typeof json.expires_in !== 'number' ? parseFloat(json.expires_in) : json.expires_in;
2131
+ assertNumber(expiresIn, false, '"response" body "expires_in" property', INVALID_RESPONSE, {
2132
+ body: json,
2133
+ });
2134
+ json.expires_in = expiresIn;
2135
+ if (json.verification_uri_complete !== undefined) {
2136
+ assertString(json.verification_uri_complete, '"response" body "verification_uri_complete" property', INVALID_RESPONSE, { body: json });
1854
2137
  }
1855
- if (json.interval !== undefined && (typeof json.interval !== 'number' || json.interval <= 0)) {
1856
- throw new OPE('"response" body "interval" property must be a positive number');
2138
+ if (json.interval !== undefined) {
2139
+ assertNumber(json.interval, false, '"response" body "interval" property', INVALID_RESPONSE, {
2140
+ body: json,
2141
+ });
1857
2142
  }
1858
2143
  return json;
1859
2144
  }
1860
- export async function deviceCodeGrantRequest(as, client, deviceCode, options) {
2145
+ export async function deviceCodeGrantRequest(as, client, clientAuthentication, deviceCode, options) {
1861
2146
  assertAs(as);
1862
2147
  assertClient(client);
1863
- if (!validateString(deviceCode)) {
1864
- throw new TypeError('"deviceCode" must be a non-empty string');
1865
- }
2148
+ assertString(deviceCode, '"deviceCode"');
1866
2149
  const parameters = new URLSearchParams(options?.additionalParameters);
1867
2150
  parameters.set('device_code', deviceCode);
1868
- return tokenEndpointRequest(as, client, 'urn:ietf:params:oauth:grant-type:device_code', parameters, options);
2151
+ return tokenEndpointRequest(as, client, clientAuthentication, 'urn:ietf:params:oauth:grant-type:device_code', parameters, options);
1869
2152
  }
1870
- export async function processDeviceCodeResponse(as, client, response) {
1871
- return processGenericAccessTokenResponse(as, client, response);
2153
+ export async function processDeviceCodeResponse(as, client, response, options) {
2154
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1872
2155
  }
1873
2156
  export async function generateKeyPair(alg, options) {
1874
- if (!validateString(alg)) {
1875
- throw new TypeError('"alg" must be a non-empty string');
1876
- }
1877
- const algorithm = algToSubtle(alg, alg === 'EdDSA' ? (options?.crv ?? 'Ed25519') : undefined);
2157
+ assertString(alg, '"alg"');
2158
+ const algorithm = algToSubtle(alg);
1878
2159
  if (alg.startsWith('PS') || alg.startsWith('RS')) {
1879
2160
  Object.assign(algorithm, {
1880
2161
  modulusLength: options?.modulusLength ?? 2048,
@@ -1892,46 +2173,53 @@ function normalizeHtu(htu) {
1892
2173
  url.hash = '';
1893
2174
  return url.href;
1894
2175
  }
1895
- async function validateDPoP(as, request, accessToken, accessTokenClaims, options) {
1896
- const header = request.headers.get('dpop');
1897
- if (header === null) {
1898
- throw new OPE('operation indicated DPoP use but the request has no DPoP HTTP Header');
2176
+ async function validateDPoP(request, accessToken, accessTokenClaims, options) {
2177
+ const headerValue = request.headers.get('dpop');
2178
+ if (headerValue === null) {
2179
+ throw OPE('operation indicated DPoP use but the request has no DPoP HTTP Header', INVALID_REQUEST, { headers: request.headers });
1899
2180
  }
1900
2181
  if (request.headers.get('authorization')?.toLowerCase().startsWith('dpop ') === false) {
1901
- throw new OPE(`operation indicated DPoP use but the request's Authorization HTTP Header scheme is not DPoP`);
2182
+ throw OPE(`operation indicated DPoP use but the request's Authorization HTTP Header scheme is not DPoP`, INVALID_REQUEST, { headers: request.headers });
1902
2183
  }
1903
2184
  if (typeof accessTokenClaims.cnf?.jkt !== 'string') {
1904
- throw new OPE('operation indicated DPoP use but the JWT Access Token has no jkt confirmation claim');
2185
+ throw OPE('operation indicated DPoP use but the JWT Access Token has no jkt confirmation claim', INVALID_REQUEST, { claims: accessTokenClaims });
1905
2186
  }
1906
2187
  const clockSkew = getClockSkew(options);
1907
- const proof = await validateJwt(header, checkSigningAlgorithm.bind(undefined, undefined, as?.dpop_signing_alg_values_supported || SUPPORTED_JWS_ALGS), async ({ jwk, alg }) => {
1908
- if (!jwk) {
1909
- throw new OPE('DPoP Proof is missing the jwk header parameter');
1910
- }
1911
- const key = await importJwk(alg, jwk);
1912
- if (key.type !== 'public') {
1913
- throw new OPE('DPoP Proof jwk header parameter must contain a public key');
1914
- }
1915
- return key;
1916
- }, clockSkew, getClockTolerance(options), undefined)
2188
+ const proof = await validateJwt(headerValue, checkSigningAlgorithm.bind(undefined, options?.signingAlgorithms, undefined, SUPPORTED_JWS_ALGS), clockSkew, getClockTolerance(options), undefined)
1917
2189
  .then(checkJwtType.bind(undefined, 'dpop+jwt'))
1918
2190
  .then(validatePresence.bind(undefined, ['iat', 'jti', 'ath', 'htm', 'htu']));
1919
2191
  const now = epochTime() + clockSkew;
1920
2192
  const diff = Math.abs(now - proof.claims.iat);
1921
2193
  if (diff > 300) {
1922
- throw new OPE('DPoP Proof iat is not recent enough');
2194
+ throw OPE('DPoP Proof iat is not recent enough', JWT_TIMESTAMP_CHECK, {
2195
+ now,
2196
+ claims: proof.claims,
2197
+ claim: 'iat',
2198
+ });
1923
2199
  }
1924
2200
  if (proof.claims.htm !== request.method) {
1925
- throw new OPE('DPoP Proof htm mismatch');
2201
+ throw OPE('DPoP Proof htm mismatch', JWT_CLAIM_COMPARISON, {
2202
+ expected: request.method,
2203
+ claims: proof.claims,
2204
+ claim: 'htm',
2205
+ });
1926
2206
  }
1927
2207
  if (typeof proof.claims.htu !== 'string' ||
1928
2208
  normalizeHtu(proof.claims.htu) !== normalizeHtu(request.url)) {
1929
- throw new OPE('DPoP Proof htu mismatch');
2209
+ throw OPE('DPoP Proof htu mismatch', JWT_CLAIM_COMPARISON, {
2210
+ expected: normalizeHtu(request.url),
2211
+ claims: proof.claims,
2212
+ claim: 'htu',
2213
+ });
1930
2214
  }
1931
2215
  {
1932
- const expected = b64u(await crypto.subtle.digest('SHA-256', encoder.encode(accessToken)));
2216
+ const expected = b64u(await crypto.subtle.digest('SHA-256', buf(accessToken)));
1933
2217
  if (proof.claims.ath !== expected) {
1934
- throw new OPE('DPoP Proof ath mismatch');
2218
+ throw OPE('DPoP Proof ath mismatch', JWT_CLAIM_COMPARISON, {
2219
+ expected,
2220
+ claims: proof.claims,
2221
+ claim: 'ath',
2222
+ });
1935
2223
  }
1936
2224
  }
1937
2225
  {
@@ -1960,25 +2248,44 @@ async function validateDPoP(as, request, accessToken, accessTokenClaims, options
1960
2248
  };
1961
2249
  break;
1962
2250
  default:
1963
- throw new UnsupportedOperationError();
2251
+ throw new UnsupportedOperationError('unsupported JWK key type', { cause: proof.header.jwk });
1964
2252
  }
1965
- const expected = b64u(await crypto.subtle.digest('SHA-256', encoder.encode(JSON.stringify(components))));
2253
+ const expected = b64u(await crypto.subtle.digest('SHA-256', buf(JSON.stringify(components))));
1966
2254
  if (accessTokenClaims.cnf.jkt !== expected) {
1967
- throw new OPE('JWT Access Token confirmation mismatch');
2255
+ throw OPE('JWT Access Token confirmation mismatch', JWT_CLAIM_COMPARISON, {
2256
+ expected,
2257
+ claims: accessTokenClaims,
2258
+ claim: 'cnf.jkt',
2259
+ });
1968
2260
  }
1969
2261
  }
2262
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = headerValue.split('.');
2263
+ const signature = b64u(encodedSignature);
2264
+ const { jwk, alg } = proof.header;
2265
+ if (!jwk) {
2266
+ throw OPE('DPoP Proof is missing the jwk header parameter', INVALID_REQUEST, {
2267
+ header: proof.header,
2268
+ });
2269
+ }
2270
+ const key = await importJwk(alg, jwk);
2271
+ if (key.type !== 'public') {
2272
+ throw OPE('DPoP Proof jwk header parameter must contain a public key', INVALID_REQUEST, {
2273
+ header: proof.header,
2274
+ });
2275
+ }
2276
+ await validateJwsSignature(protectedHeader, payload, key, signature);
1970
2277
  }
1971
2278
  export async function validateJwtAccessToken(as, request, expectedAudience, options) {
1972
2279
  assertAs(as);
1973
2280
  if (!looseInstanceOf(request, Request)) {
1974
- throw new TypeError('"request" must be an instance of Request');
1975
- }
1976
- if (!validateString(expectedAudience)) {
1977
- throw new OPE('"expectedAudience" must be a non-empty string');
2281
+ throw CodedTypeError('"request" must be an instance of Request', ERR_INVALID_ARG_TYPE);
1978
2282
  }
2283
+ assertString(expectedAudience, '"expectedAudience"');
1979
2284
  const authorization = request.headers.get('authorization');
1980
2285
  if (authorization === null) {
1981
- throw new OPE('"request" is missing an Authorization HTTP Header');
2286
+ throw OPE('"request" is missing an Authorization HTTP Header', INVALID_REQUEST, {
2287
+ headers: request.headers,
2288
+ });
1982
2289
  }
1983
2290
  let { 0: scheme, 1: accessToken, length } = authorization.split(' ');
1984
2291
  scheme = scheme.toLowerCase();
@@ -1987,10 +2294,14 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
1987
2294
  case 'bearer':
1988
2295
  break;
1989
2296
  default:
1990
- throw new UnsupportedOperationError('unsupported Authorization HTTP Header scheme');
2297
+ throw new UnsupportedOperationError('unsupported Authorization HTTP Header scheme', {
2298
+ cause: { headers: request.headers },
2299
+ });
1991
2300
  }
1992
2301
  if (length !== 2) {
1993
- throw new OPE('invalid Authorization HTTP Header format');
2302
+ throw OPE('invalid Authorization HTTP Header format', INVALID_REQUEST, {
2303
+ headers: request.headers,
2304
+ });
1994
2305
  }
1995
2306
  const requiredClaims = [
1996
2307
  'iss',
@@ -2004,43 +2315,54 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
2004
2315
  if (options?.requireDPoP || scheme === 'dpop' || request.headers.has('dpop')) {
2005
2316
  requiredClaims.push('cnf');
2006
2317
  }
2007
- const { claims } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, undefined, SUPPORTED_JWS_ALGS), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(options), getClockTolerance(options), undefined)
2318
+ const { claims, header } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, options?.signingAlgorithms, undefined, SUPPORTED_JWS_ALGS), getClockSkew(options), getClockTolerance(options), undefined)
2008
2319
  .then(checkJwtType.bind(undefined, 'at+jwt'))
2009
2320
  .then(validatePresence.bind(undefined, requiredClaims))
2010
- .then(validateIssuer.bind(undefined, as.issuer))
2011
- .then(validateAudience.bind(undefined, expectedAudience));
2321
+ .then(validateIssuer.bind(undefined, as))
2322
+ .then(validateAudience.bind(undefined, expectedAudience))
2323
+ .catch(reassignRSCode);
2012
2324
  for (const claim of ['client_id', 'jti', 'sub']) {
2013
2325
  if (typeof claims[claim] !== 'string') {
2014
- throw new OPE(`unexpected JWT "${claim}" claim type`);
2326
+ throw OPE(`unexpected JWT "${claim}" claim type`, INVALID_REQUEST, { claims });
2015
2327
  }
2016
2328
  }
2017
2329
  if ('cnf' in claims) {
2018
2330
  if (!isJsonObject(claims.cnf)) {
2019
- throw new OPE('unexpected JWT "cnf" (confirmation) claim value');
2331
+ throw OPE('unexpected JWT "cnf" (confirmation) claim value', INVALID_REQUEST, { claims });
2020
2332
  }
2021
2333
  const { 0: cnf, length } = Object.keys(claims.cnf);
2022
2334
  if (length) {
2023
2335
  if (length !== 1) {
2024
- throw new UnsupportedOperationError('multiple confirmation claims are not supported');
2336
+ throw new UnsupportedOperationError('multiple confirmation claims are not supported', {
2337
+ cause: { claims },
2338
+ });
2025
2339
  }
2026
2340
  if (cnf !== 'jkt') {
2027
- throw new UnsupportedOperationError('unsupported JWT Confirmation method');
2341
+ throw new UnsupportedOperationError('unsupported JWT Confirmation method', {
2342
+ cause: { claims },
2343
+ });
2028
2344
  }
2029
2345
  }
2030
2346
  }
2347
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = accessToken.split('.');
2348
+ const signature = b64u(encodedSignature);
2349
+ const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
2350
+ await validateJwsSignature(protectedHeader, payload, key, signature);
2031
2351
  if (options?.requireDPoP ||
2032
2352
  scheme === 'dpop' ||
2033
2353
  claims.cnf?.jkt !== undefined ||
2034
2354
  request.headers.has('dpop')) {
2035
- await validateDPoP(as, request, accessToken, claims, options);
2355
+ await validateDPoP(request, accessToken, claims, options).catch(reassignRSCode);
2036
2356
  }
2037
2357
  return claims;
2038
2358
  }
2039
- export const experimentalCustomFetch = customFetch;
2040
- export const experimental_customFetch = customFetch;
2041
- export const experimentalUseMtlsAlias = useMtlsAlias;
2042
- export const experimental_useMtlsAlias = useMtlsAlias;
2043
- export const experimental_validateDetachedSignatureResponse = (...args) => validateDetachedSignatureResponse(...args);
2044
- export const experimental_validateJwtAccessToken = (...args) => validateJwtAccessToken(...args);
2045
- export const validateJwtUserinfoSignature = (...args) => validateJwtUserInfoSignature(...args);
2046
- export const experimental_jwksCache = jwksCache;
2359
+ function reassignRSCode(err) {
2360
+ if (err instanceof OperationProcessingError && err?.code === INVALID_REQUEST) {
2361
+ err.code = INVALID_RESPONSE;
2362
+ }
2363
+ throw err;
2364
+ }
2365
+ export const _nopkce = Symbol();
2366
+ export const _nodiscoverycheck = Symbol();
2367
+ export const _expectedIssuer = Symbol();
2368
+ //# sourceMappingURL=index.js.map