oauth4webapi 2.17.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.0';
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 private 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,86 @@ 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
+ assertNumber(json.expires_in, false, '"response" body "expires_in" property', INVALID_RESPONSE, {
821
+ body: json,
822
+ });
707
823
  return json;
708
824
  }
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');
825
+ function assertDPoP(option) {
826
+ if (!branded.has(option)) {
827
+ throw CodedTypeError('"options.DPoP" is not a valid DPoPHandle', ERR_INVALID_ARG_VALUE);
712
828
  }
829
+ }
830
+ async function resourceRequest(accessToken, method, url, headers, body, options) {
831
+ assertString(accessToken, '"accessToken"');
713
832
  if (!(url instanceof URL)) {
714
- throw new TypeError('"url" must be an instance of URL');
833
+ throw CodedTypeError('"url" must be an instance of URL', ERR_INVALID_ARG_TYPE);
715
834
  }
835
+ checkProtocol(url, options?.[allowInsecureRequests] !== true);
716
836
  headers = prepareHeaders(headers);
717
- if (options?.DPoP === undefined) {
718
- headers.set('authorization', `Bearer ${accessToken}`);
837
+ if (options?.DPoP) {
838
+ assertDPoP(options.DPoP);
839
+ await options.DPoP.addProof(url, headers, method.toUpperCase(), accessToken);
840
+ headers.set('authorization', `DPoP ${accessToken}`);
719
841
  }
720
842
  else {
721
- await dpopProofJwt(headers, options.DPoP, url, method.toUpperCase(), getClockSkew({ [clockSkew]: options?.[clockSkew] }), accessToken);
722
- headers.set('authorization', `DPoP ${accessToken}`);
843
+ headers.set('authorization', `Bearer ${accessToken}`);
723
844
  }
724
- return (options?.[customFetch] || fetch)(url.href, {
845
+ const response = await (options?.[customFetch] || fetch)(url.href, {
725
846
  body,
726
847
  headers: Object.fromEntries(headers.entries()),
727
848
  method,
728
849
  redirect: 'manual',
729
- signal: options?.signal ? signal(options.signal) : null,
730
- }).then(processDpopNonce);
850
+ signal: options?.signal ? signal(options.signal) : undefined,
851
+ });
852
+ options?.DPoP?.cacheNonce(response);
853
+ return response;
854
+ }
855
+ export async function protectedResourceRequest(accessToken, method, url, headers, body, options) {
856
+ return resourceRequest(accessToken, method, url, headers, body, options).then((response) => {
857
+ let challenges;
858
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
859
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
860
+ }
861
+ return response;
862
+ });
731
863
  }
732
864
  export async function userInfoRequest(as, client, accessToken, options) {
733
865
  assertAs(as);
734
866
  assertClient(client);
735
- const url = resolveEndpoint(as, 'userinfo_endpoint', alias(client, options));
867
+ const url = resolveEndpoint(as, 'userinfo_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
736
868
  const headers = prepareHeaders(options?.headers);
737
869
  if (client.userinfo_signed_response_alg) {
738
870
  headers.set('accept', 'application/jwt');
@@ -741,14 +873,14 @@ export async function userInfoRequest(as, client, accessToken, options) {
741
873
  headers.set('accept', 'application/json');
742
874
  headers.append('accept', 'application/jwt');
743
875
  }
744
- return protectedResourceRequest(accessToken, 'GET', url, headers, null, {
876
+ return resourceRequest(accessToken, 'GET', url, headers, null, {
745
877
  ...options,
746
878
  [clockSkew]: getClockSkew(client),
747
879
  });
748
880
  }
749
881
  let jwksMap;
750
882
  function setJwksCache(as, jwks, uat, cache) {
751
- jwksMap || (jwksMap = new WeakMap());
883
+ jwksMap ||= new WeakMap();
752
884
  jwksMap.set(as, {
753
885
  jwks,
754
886
  uat,
@@ -782,7 +914,7 @@ function clearJwksCache(as, cache) {
782
914
  }
783
915
  async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
784
916
  const { alg, kid } = header;
785
- checkSupportedJwsAlg(alg);
917
+ checkSupportedJwsAlg(header);
786
918
  if (!jwksMap?.has(as) && isFreshJwksCache(options?.[jwksCache])) {
787
919
  setJwksCache(as, options?.[jwksCache].jwks, options?.[jwksCache].uat);
788
920
  }
@@ -814,7 +946,7 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
814
946
  kty = 'OKP';
815
947
  break;
816
948
  default:
817
- throw new UnsupportedOperationError();
949
+ throw new UnsupportedOperationError('unsupported JWS algorithm', { cause: { alg } });
818
950
  }
819
951
  const candidates = jwks.keys.filter((jwk) => {
820
952
  if (jwk.kty !== kty) {
@@ -836,7 +968,8 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
836
968
  case alg === 'ES256' && jwk.crv !== 'P-256':
837
969
  case alg === 'ES384' && jwk.crv !== 'P-384':
838
970
  case alg === 'ES512' && jwk.crv !== 'P-521':
839
- case alg === 'EdDSA' && !(jwk.crv === 'Ed25519' || jwk.crv === 'Ed448'):
971
+ case alg === 'Ed25519' && jwk.crv !== 'Ed25519':
972
+ case alg === 'EdDSA' && jwk.crv !== 'Ed25519':
840
973
  return false;
841
974
  }
842
975
  return true;
@@ -847,221 +980,219 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
847
980
  clearJwksCache(as, options?.[jwksCache]);
848
981
  return getPublicSigKeyFromIssuerJwksUri(as, options, header);
849
982
  }
850
- throw new OPE('error when selecting a JWT verification key, no applicable keys found');
983
+ 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
984
  }
852
985
  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');
986
+ 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
987
  }
859
- return key;
988
+ return importJwk(alg, jwk);
860
989
  }
861
990
  export const skipSubjectCheck = Symbol();
862
991
  function getContentType(response) {
863
992
  return response.headers.get('content-type')?.split(';')[0];
864
993
  }
865
- export async function processUserInfoResponse(as, client, expectedSubject, response) {
994
+ export async function processUserInfoResponse(as, client, expectedSubject, response, options) {
866
995
  assertAs(as);
867
996
  assertClient(client);
868
997
  if (!looseInstanceOf(response, Response)) {
869
- throw new TypeError('"response" must be an instance of Response');
998
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
999
+ }
1000
+ let challenges;
1001
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1002
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
870
1003
  }
871
1004
  if (response.status !== 200) {
872
- throw new OPE('"response" is not a conform UserInfo Endpoint response');
1005
+ throw OPE('"response" is not a conform UserInfo Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
873
1006
  }
1007
+ assertReadableResponse(response);
874
1008
  let json;
875
1009
  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])
1010
+ 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
1011
  .then(validateOptionalAudience.bind(undefined, client.client_id))
879
- .then(validateOptionalIssuer.bind(undefined, as.issuer));
880
- jwtResponseBodies.set(response, jwt);
1012
+ .then(validateOptionalIssuer.bind(undefined, as));
1013
+ jwtRefs.set(response, jwt);
881
1014
  json = claims;
882
1015
  }
883
1016
  else {
884
1017
  if (client.userinfo_signed_response_alg) {
885
- throw new OPE('JWT UserInfo Response expected');
1018
+ throw OPE('JWT UserInfo Response expected', JWT_USERINFO_EXPECTED, response);
886
1019
  }
887
- assertReadableResponse(response);
1020
+ assertApplicationJson(response);
888
1021
  try {
889
1022
  json = await response.json();
890
1023
  }
891
1024
  catch (cause) {
892
- throw new OPE('failed to parse "response" body as JSON', { cause });
1025
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
893
1026
  }
894
1027
  }
895
1028
  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');
1029
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
900
1030
  }
1031
+ assertString(json.sub, '"response" body "sub" property', INVALID_RESPONSE, { body: json });
901
1032
  switch (expectedSubject) {
902
1033
  case skipSubjectCheck:
903
1034
  break;
904
1035
  default:
905
- if (!validateString(expectedSubject)) {
906
- throw new OPE('"expectedSubject" must be a non-empty string');
907
- }
1036
+ assertString(expectedSubject, '"expectedSubject"');
908
1037
  if (json.sub !== expectedSubject) {
909
- throw new OPE('unexpected "response" body "sub" value');
1038
+ throw OPE('unexpected "response" body "sub" property value', JSON_ATTRIBUTE_COMPARISON, {
1039
+ expected: expectedSubject,
1040
+ body: json,
1041
+ attribute: 'sub',
1042
+ });
910
1043
  }
911
1044
  }
912
1045
  return json;
913
1046
  }
914
- async function authenticatedRequest(as, client, method, url, body, headers, options) {
915
- await clientAuthentication(as, client, body, headers, options?.clientPrivateKey);
1047
+ async function authenticatedRequest(as, client, clientAuthentication, url, body, headers, options) {
1048
+ await clientAuthentication(as, client, body, headers);
916
1049
  headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
917
1050
  return (options?.[customFetch] || fetch)(url.href, {
918
1051
  body,
919
1052
  headers: Object.fromEntries(headers.entries()),
920
- method,
1053
+ method: 'POST',
921
1054
  redirect: 'manual',
922
- signal: options?.signal ? signal(options.signal) : null,
923
- }).then(processDpopNonce);
1055
+ signal: options?.signal ? signal(options.signal) : undefined,
1056
+ });
924
1057
  }
925
- async function tokenEndpointRequest(as, client, grantType, parameters, options) {
926
- const url = resolveEndpoint(as, 'token_endpoint', alias(client, options));
1058
+ async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
1059
+ const url = resolveEndpoint(as, 'token_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
927
1060
  parameters.set('grant_type', grantType);
928
1061
  const headers = prepareHeaders(options?.headers);
929
1062
  headers.set('accept', 'application/json');
930
1063
  if (options?.DPoP !== undefined) {
931
- await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client));
1064
+ assertDPoP(options.DPoP);
1065
+ await options.DPoP.addProof(url, headers, 'POST');
932
1066
  }
933
- return authenticatedRequest(as, client, 'POST', url, parameters, headers, options);
1067
+ const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers, options);
1068
+ options?.DPoP?.cacheNonce(response);
1069
+ return response;
934
1070
  }
935
- export async function refreshTokenGrantRequest(as, client, refreshToken, options) {
1071
+ export async function refreshTokenGrantRequest(as, client, clientAuthentication, refreshToken, options) {
936
1072
  assertAs(as);
937
1073
  assertClient(client);
938
- if (!validateString(refreshToken)) {
939
- throw new TypeError('"refreshToken" must be a non-empty string');
940
- }
1074
+ assertString(refreshToken, '"refreshToken"');
941
1075
  const parameters = new URLSearchParams(options?.additionalParameters);
942
1076
  parameters.set('refresh_token', refreshToken);
943
- return tokenEndpointRequest(as, client, 'refresh_token', parameters, options);
1077
+ return tokenEndpointRequest(as, client, clientAuthentication, 'refresh_token', parameters, options);
944
1078
  }
945
1079
  const idTokenClaims = new WeakMap();
946
- const jwtResponseBodies = new WeakMap();
1080
+ const jwtRefs = new WeakMap();
947
1081
  export function getValidatedIdTokenClaims(ref) {
948
1082
  if (!ref.id_token) {
949
1083
  return undefined;
950
1084
  }
951
1085
  const claims = idTokenClaims.get(ref);
952
1086
  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');
1087
+ throw CodedTypeError('"ref" was already garbage collected or did not resolve from the proper sources', ERR_INVALID_ARG_VALUE);
961
1088
  }
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));
1089
+ return claims;
970
1090
  }
971
- async function validateJwtResponseSignature(as, ref, options) {
1091
+ export async function validateApplicationLevelSignature(as, ref, options) {
972
1092
  assertAs(as);
973
- if (!jwtResponseBodies.has(ref)) {
974
- throw new OPE('"ref" does not contain a processed JWT Response to verify the signature of');
1093
+ if (!jwtRefs.has(ref)) {
1094
+ throw CodedTypeError('"ref" does not contain a processed JWT Response to verify the signature of', ERR_INVALID_ARG_VALUE);
975
1095
  }
976
- const { 0: protectedHeader, 1: payload, 2: encodedSignature, } = jwtResponseBodies.get(ref).split('.');
1096
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwtRefs.get(ref).split('.');
977
1097
  const header = JSON.parse(buf(b64u(protectedHeader)));
978
1098
  if (header.alg.startsWith('HS')) {
979
- throw new UnsupportedOperationError();
1099
+ throw new UnsupportedOperationError('unsupported JWS algorithm', { cause: { alg: header.alg } });
980
1100
  }
981
1101
  let key;
982
1102
  key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
983
1103
  await validateJwsSignature(protectedHeader, payload, key, b64u(encodedSignature));
984
1104
  }
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) {
1105
+ async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, options) {
992
1106
  assertAs(as);
993
1107
  assertClient(client);
994
1108
  if (!looseInstanceOf(response, Response)) {
995
- throw new TypeError('"response" must be an instance of Response');
1109
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1110
+ }
1111
+ let challenges;
1112
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1113
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
996
1114
  }
997
1115
  if (response.status !== 200) {
998
1116
  let err;
999
1117
  if ((err = await handleOAuthBodyError(response))) {
1000
- return err;
1118
+ await response.body?.cancel();
1119
+ throw new ResponseBodyError('server responded with an error in the response body', {
1120
+ cause: err,
1121
+ response,
1122
+ });
1001
1123
  }
1002
- throw new OPE('"response" is not a conform Token Endpoint response');
1124
+ throw OPE('"response" is not a conform Token Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1003
1125
  }
1004
1126
  assertReadableResponse(response);
1127
+ assertApplicationJson(response);
1005
1128
  let json;
1006
1129
  try {
1007
1130
  json = await response.json();
1008
1131
  }
1009
1132
  catch (cause) {
1010
- throw new OPE('failed to parse "response" body as JSON', { cause });
1133
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1011
1134
  }
1012
1135
  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');
1136
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1020
1137
  }
1138
+ assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
1139
+ body: json,
1140
+ });
1141
+ assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
1142
+ body: json,
1143
+ });
1021
1144
  json.token_type = json.token_type.toLowerCase();
1022
1145
  if (json.token_type !== 'dpop' && json.token_type !== 'bearer') {
1023
- throw new UnsupportedOperationError('unsupported `token_type` value');
1146
+ throw new UnsupportedOperationError('unsupported `token_type` value', { cause: { body: json } });
1024
1147
  }
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');
1148
+ if (json.expires_in !== undefined) {
1149
+ assertNumber(json.expires_in, false, '"response" body "expires_in" property', INVALID_RESPONSE, { body: json });
1028
1150
  }
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');
1151
+ if (json.refresh_token !== undefined) {
1152
+ assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
1153
+ body: json,
1154
+ });
1033
1155
  }
1034
1156
  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
- }
1157
+ throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, { body: json });
1158
+ }
1159
+ if (json.id_token !== undefined) {
1160
+ assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
1161
+ body: json,
1162
+ });
1163
+ const requiredClaims = ['aud', 'exp', 'iat', 'iss', 'sub'];
1164
+ if (client.require_auth_time === true) {
1165
+ requiredClaims.push('auth_time');
1166
+ }
1167
+ if (client.default_max_age !== undefined) {
1168
+ assertNumber(client.default_max_age, false, '"client.default_max_age"');
1169
+ requiredClaims.push('auth_time');
1170
+ }
1171
+ if (additionalRequiredIdTokenClaims?.length) {
1172
+ requiredClaims.push(...additionalRequiredIdTokenClaims);
1173
+ }
1174
+ 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])
1175
+ .then(validatePresence.bind(undefined, requiredClaims))
1176
+ .then(validateIssuer.bind(undefined, as))
1177
+ .then(validateAudience.bind(undefined, client.client_id));
1178
+ if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
1179
+ if (claims.azp === undefined) {
1180
+ throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: 'aud' });
1053
1181
  }
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');
1182
+ if (claims.azp !== client.client_id) {
1183
+ throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, { expected: client.client_id, claims, claim: 'azp' });
1057
1184
  }
1058
- idTokenClaims.set(json, [claims, jwt]);
1059
1185
  }
1186
+ if (claims.auth_time !== undefined) {
1187
+ assertNumber(claims.auth_time, false, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
1188
+ }
1189
+ jwtRefs.set(response, jwt);
1190
+ idTokenClaims.set(json, claims);
1060
1191
  }
1061
1192
  return json;
1062
1193
  }
1063
- export async function processRefreshTokenResponse(as, client, response) {
1064
- return processGenericAccessTokenResponse(as, client, response);
1194
+ export async function processRefreshTokenResponse(as, client, response, options) {
1195
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1065
1196
  }
1066
1197
  function validateOptionalAudience(expected, result) {
1067
1198
  if (result.claims.aud !== undefined) {
@@ -1072,23 +1203,36 @@ function validateOptionalAudience(expected, result) {
1072
1203
  function validateAudience(expected, result) {
1073
1204
  if (Array.isArray(result.claims.aud)) {
1074
1205
  if (!result.claims.aud.includes(expected)) {
1075
- throw new OPE('unexpected JWT "aud" (audience) claim value');
1206
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
1207
+ expected,
1208
+ claims: result.claims,
1209
+ claim: 'aud',
1210
+ });
1076
1211
  }
1077
1212
  }
1078
1213
  else if (result.claims.aud !== expected) {
1079
- throw new OPE('unexpected JWT "aud" (audience) claim value');
1214
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
1215
+ expected,
1216
+ claims: result.claims,
1217
+ claim: 'aud',
1218
+ });
1080
1219
  }
1081
1220
  return result;
1082
1221
  }
1083
- function validateOptionalIssuer(expected, result) {
1222
+ function validateOptionalIssuer(as, result) {
1084
1223
  if (result.claims.iss !== undefined) {
1085
- return validateIssuer(expected, result);
1224
+ return validateIssuer(as, result);
1086
1225
  }
1087
1226
  return result;
1088
1227
  }
1089
- function validateIssuer(expected, result) {
1228
+ function validateIssuer(as, result) {
1229
+ const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
1090
1230
  if (result.claims.iss !== expected) {
1091
- throw new OPE('unexpected JWT "iss" (issuer) claim value');
1231
+ throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
1232
+ expected,
1233
+ claims: result.claims,
1234
+ claim: 'iss',
1235
+ });
1092
1236
  }
1093
1237
  return result;
1094
1238
  }
@@ -1097,27 +1241,25 @@ function brand(searchParams) {
1097
1241
  branded.add(searchParams);
1098
1242
  return searchParams;
1099
1243
  }
1100
- export async function authorizationCodeGrantRequest(as, client, callbackParameters, redirectUri, codeVerifier, options) {
1244
+ export async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
1101
1245
  assertAs(as);
1102
1246
  assertClient(client);
1103
1247
  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');
1248
+ throw CodedTypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()', ERR_INVALID_ARG_VALUE);
1111
1249
  }
1250
+ assertString(redirectUri, '"redirectUri"');
1112
1251
  const code = getURLSearchParameter(callbackParameters, 'code');
1113
1252
  if (!code) {
1114
- throw new OPE('no authorization code in "callbackParameters"');
1253
+ throw OPE('no authorization code in "callbackParameters"', INVALID_RESPONSE);
1115
1254
  }
1116
1255
  const parameters = new URLSearchParams(options?.additionalParameters);
1117
1256
  parameters.set('redirect_uri', redirectUri);
1118
- parameters.set('code_verifier', codeVerifier);
1119
1257
  parameters.set('code', code);
1120
- return tokenEndpointRequest(as, client, 'authorization_code', parameters, options);
1258
+ if (codeVerifier !== _nopkce) {
1259
+ assertString(codeVerifier, '"codeVerifier"');
1260
+ parameters.set('code_verifier', codeVerifier);
1261
+ }
1262
+ return tokenEndpointRequest(as, client, clientAuthentication, 'authorization_code', parameters, options);
1121
1263
  }
1122
1264
  const jwtClaimNames = {
1123
1265
  aud: 'audience',
@@ -1134,138 +1276,190 @@ const jwtClaimNames = {
1134
1276
  htm: 'http method',
1135
1277
  htu: 'http uri',
1136
1278
  cnf: 'confirmation',
1279
+ auth_time: 'authentication time',
1137
1280
  };
1138
1281
  function validatePresence(required, result) {
1139
1282
  for (const claim of required) {
1140
1283
  if (result.claims[claim] === undefined) {
1141
- throw new OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`);
1284
+ throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
1285
+ claims: result.claims,
1286
+ });
1142
1287
  }
1143
1288
  }
1144
1289
  return result;
1145
1290
  }
1146
1291
  export const expectNoNonce = Symbol();
1147
1292
  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;
1293
+ export async function processAuthorizationCodeResponse(as, client, response, options) {
1294
+ if (typeof options?.expectedNonce === 'string' ||
1295
+ typeof options?.maxAge === 'number' ||
1296
+ options?.requireIdToken) {
1297
+ return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, {
1298
+ [jweDecrypt]: options[jweDecrypt],
1299
+ });
1152
1300
  }
1153
- if (!validateString(result.id_token)) {
1154
- throw new OPE('"response" body "id_token" property must be a non-empty string');
1301
+ return processAuthorizationCodeOAuth2Response(as, client, response, options);
1302
+ }
1303
+ async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, options) {
1304
+ const additionalRequiredClaims = [];
1305
+ switch (expectedNonce) {
1306
+ case undefined:
1307
+ expectedNonce = expectNoNonce;
1308
+ break;
1309
+ case expectNoNonce:
1310
+ break;
1311
+ default:
1312
+ assertString(expectedNonce, '"expectedNonce" argument');
1313
+ additionalRequiredClaims.push('nonce');
1155
1314
  }
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');
1315
+ maxAge ??= client.default_max_age;
1316
+ switch (maxAge) {
1317
+ case undefined:
1318
+ maxAge = skipAuthTimeCheck;
1319
+ break;
1320
+ case skipAuthTimeCheck:
1321
+ break;
1322
+ default:
1323
+ assertNumber(maxAge, false, '"maxAge" argument');
1324
+ additionalRequiredClaims.push('auth_time');
1161
1325
  }
1326
+ const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, options);
1327
+ assertString(result.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
1328
+ body: result,
1329
+ });
1330
+ const claims = getValidatedIdTokenClaims(result);
1162
1331
  if (maxAge !== skipAuthTimeCheck) {
1163
- if (typeof maxAge !== 'number' || maxAge < 0) {
1164
- throw new TypeError('"maxAge" must be a non-negative number');
1165
- }
1166
1332
  const now = epochTime() + getClockSkew(client);
1167
1333
  const tolerance = getClockTolerance(client);
1168
1334
  if (claims.auth_time + maxAge < now - tolerance) {
1169
- throw new OPE('too much time has elapsed since the last End-User authentication');
1335
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: 'auth_time' });
1170
1336
  }
1171
1337
  }
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
- }
1338
+ if (expectedNonce === expectNoNonce) {
1339
+ if (claims.nonce !== undefined) {
1340
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1341
+ expected: undefined,
1342
+ claims,
1343
+ claim: 'nonce',
1344
+ });
1345
+ }
1346
+ }
1347
+ else if (claims.nonce !== expectedNonce) {
1348
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1349
+ expected: expectedNonce,
1350
+ claims,
1351
+ claim: 'nonce',
1352
+ });
1189
1353
  }
1190
1354
  return result;
1191
1355
  }
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');
1356
+ async function processAuthorizationCodeOAuth2Response(as, client, response, options) {
1357
+ const result = await processGenericAccessTokenResponse(as, client, response, undefined, options);
1358
+ const claims = getValidatedIdTokenClaims(result);
1359
+ if (claims) {
1360
+ if (client.default_max_age !== undefined) {
1361
+ assertNumber(client.default_max_age, false, '"client.default_max_age"');
1362
+ const now = epochTime() + getClockSkew(client);
1363
+ const tolerance = getClockTolerance(client);
1364
+ if (claims.auth_time + client.default_max_age < now - tolerance) {
1365
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: 'auth_time' });
1366
+ }
1367
+ }
1368
+ if (claims.nonce !== undefined) {
1369
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1370
+ expected: undefined,
1371
+ claims,
1372
+ claim: 'nonce',
1373
+ });
1200
1374
  }
1201
- delete result.id_token;
1202
1375
  }
1203
1376
  return result;
1204
1377
  }
1378
+ export const WWW_AUTHENTICATE_CHALLENGE = 'OAUTH_WWW_AUTHENTICATE_CHALLENGE';
1379
+ export const RESPONSE_BODY_ERROR = 'OAUTH_RESPONSE_BODY_ERROR';
1380
+ export const UNSUPPORTED_OPERATION = 'OAUTH_UNSUPPORTED_OPERATION';
1381
+ export const AUTHORIZATION_RESPONSE_ERROR = 'OAUTH_AUTHORIZATION_RESPONSE_ERROR';
1382
+ export const JWT_USERINFO_EXPECTED = 'OAUTH_JWT_USERINFO_EXPECTED';
1383
+ export const PARSE_ERROR = 'OAUTH_PARSE_ERROR';
1384
+ export const INVALID_RESPONSE = 'OAUTH_INVALID_RESPONSE';
1385
+ export const INVALID_REQUEST = 'OAUTH_INVALID_REQUEST';
1386
+ export const RESPONSE_IS_NOT_JSON = 'OAUTH_RESPONSE_IS_NOT_JSON';
1387
+ export const RESPONSE_IS_NOT_CONFORM = 'OAUTH_RESPONSE_IS_NOT_CONFORM';
1388
+ export const HTTP_REQUEST_FORBIDDEN = 'OAUTH_HTTP_REQUEST_FORBIDDEN';
1389
+ export const REQUEST_PROTOCOL_FORBIDDEN = 'OAUTH_REQUEST_PROTOCOL_FORBIDDEN';
1390
+ export const JWT_TIMESTAMP_CHECK = 'OAUTH_JWT_TIMESTAMP_CHECK_FAILED';
1391
+ export const JWT_CLAIM_COMPARISON = 'OAUTH_JWT_CLAIM_COMPARISON_FAILED';
1392
+ export const JSON_ATTRIBUTE_COMPARISON = 'OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED';
1393
+ export const KEY_SELECTION = 'OAUTH_KEY_SELECTION_FAILED';
1394
+ export const MISSING_SERVER_METADATA = 'OAUTH_MISSING_SERVER_METADATA';
1395
+ export const INVALID_SERVER_METADATA = 'OAUTH_INVALID_SERVER_METADATA';
1205
1396
  function checkJwtType(expected, result) {
1206
1397
  if (typeof result.header.typ !== 'string' || normalizeTyp(result.header.typ) !== expected) {
1207
- throw new OPE('unexpected JWT "typ" header parameter value');
1398
+ throw OPE('unexpected JWT "typ" header parameter value', INVALID_RESPONSE, {
1399
+ header: result.header,
1400
+ });
1208
1401
  }
1209
1402
  return result;
1210
1403
  }
1211
- export async function clientCredentialsGrantRequest(as, client, parameters, options) {
1404
+ export async function clientCredentialsGrantRequest(as, client, clientAuthentication, parameters, options) {
1212
1405
  assertAs(as);
1213
1406
  assertClient(client);
1214
- return tokenEndpointRequest(as, client, 'client_credentials', new URLSearchParams(parameters), options);
1407
+ return tokenEndpointRequest(as, client, clientAuthentication, 'client_credentials', new URLSearchParams(parameters), options);
1215
1408
  }
1216
- export async function genericTokenEndpointRequest(as, client, grantType, parameters, options) {
1409
+ export async function genericTokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
1217
1410
  assertAs(as);
1218
1411
  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);
1412
+ assertString(grantType, '"grantType"');
1413
+ return tokenEndpointRequest(as, client, clientAuthentication, grantType, new URLSearchParams(parameters), options);
1223
1414
  }
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;
1415
+ export async function processGenericTokenEndpointResponse(as, client, response, options) {
1416
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1230
1417
  }
1231
- export async function revocationRequest(as, client, token, options) {
1418
+ export async function processClientCredentialsResponse(as, client, response, options) {
1419
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1420
+ }
1421
+ export async function revocationRequest(as, client, clientAuthentication, token, options) {
1232
1422
  assertAs(as);
1233
1423
  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));
1424
+ assertString(token, '"token"');
1425
+ const url = resolveEndpoint(as, 'revocation_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1238
1426
  const body = new URLSearchParams(options?.additionalParameters);
1239
1427
  body.set('token', token);
1240
1428
  const headers = prepareHeaders(options?.headers);
1241
1429
  headers.delete('accept');
1242
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
1430
+ return authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
1243
1431
  }
1244
1432
  export async function processRevocationResponse(response) {
1245
1433
  if (!looseInstanceOf(response, Response)) {
1246
- throw new TypeError('"response" must be an instance of Response');
1434
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1435
+ }
1436
+ let challenges;
1437
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1438
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
1247
1439
  }
1248
1440
  if (response.status !== 200) {
1249
1441
  let err;
1250
1442
  if ((err = await handleOAuthBodyError(response))) {
1251
- return err;
1443
+ await response.body?.cancel();
1444
+ throw new ResponseBodyError('server responded with an error in the response body', {
1445
+ cause: err,
1446
+ response,
1447
+ });
1252
1448
  }
1253
- throw new OPE('"response" is not a conform Revocation Endpoint response');
1449
+ throw OPE('"response" is not a conform Revocation Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1254
1450
  }
1255
1451
  return undefined;
1256
1452
  }
1257
1453
  function assertReadableResponse(response) {
1258
1454
  if (response.bodyUsed) {
1259
- throw new TypeError('"response" body has been used already');
1455
+ throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
1260
1456
  }
1261
1457
  }
1262
- export async function introspectionRequest(as, client, token, options) {
1458
+ export async function introspectionRequest(as, client, clientAuthentication, token, options) {
1263
1459
  assertAs(as);
1264
1460
  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));
1461
+ assertString(token, '"token"');
1462
+ const url = resolveEndpoint(as, 'introspection_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1269
1463
  const body = new URLSearchParams(options?.additionalParameters);
1270
1464
  body.set('token', token);
1271
1465
  const headers = prepareHeaders(options?.headers);
@@ -1275,109 +1469,113 @@ export async function introspectionRequest(as, client, token, options) {
1275
1469
  else {
1276
1470
  headers.set('accept', 'application/json');
1277
1471
  }
1278
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
1472
+ return authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
1279
1473
  }
1280
- export async function processIntrospectionResponse(as, client, response) {
1474
+ export async function processIntrospectionResponse(as, client, response, options) {
1281
1475
  assertAs(as);
1282
1476
  assertClient(client);
1283
1477
  if (!looseInstanceOf(response, Response)) {
1284
- throw new TypeError('"response" must be an instance of Response');
1478
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1479
+ }
1480
+ let challenges;
1481
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
1482
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
1285
1483
  }
1286
1484
  if (response.status !== 200) {
1287
1485
  let err;
1288
1486
  if ((err = await handleOAuthBodyError(response))) {
1289
- return err;
1487
+ await response.body?.cancel();
1488
+ throw new ResponseBodyError('server responded with an error in the response body', {
1489
+ cause: err,
1490
+ response,
1491
+ });
1290
1492
  }
1291
- throw new OPE('"response" is not a conform Introspection Endpoint response');
1493
+ throw OPE('"response" is not a conform Introspection Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1292
1494
  }
1293
1495
  let json;
1294
1496
  if (getContentType(response) === 'application/token-introspection+jwt') {
1295
1497
  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])
1498
+ 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
1499
  .then(checkJwtType.bind(undefined, 'token-introspection+jwt'))
1298
1500
  .then(validatePresence.bind(undefined, ['aud', 'iat', 'iss']))
1299
- .then(validateIssuer.bind(undefined, as.issuer))
1501
+ .then(validateIssuer.bind(undefined, as))
1300
1502
  .then(validateAudience.bind(undefined, client.client_id));
1301
- jwtResponseBodies.set(response, jwt);
1503
+ jwtRefs.set(response, jwt);
1302
1504
  json = claims.token_introspection;
1303
1505
  if (!isJsonObject(json)) {
1304
- throw new OPE('JWT "token_introspection" claim must be a JSON object');
1506
+ throw OPE('JWT "token_introspection" claim must be a JSON object', INVALID_RESPONSE, {
1507
+ claims,
1508
+ });
1305
1509
  }
1306
1510
  }
1307
1511
  else {
1308
1512
  assertReadableResponse(response);
1513
+ assertApplicationJson(response);
1309
1514
  try {
1310
1515
  json = await response.json();
1311
1516
  }
1312
1517
  catch (cause) {
1313
- throw new OPE('failed to parse "response" body as JSON', { cause });
1518
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1314
1519
  }
1315
1520
  if (!isJsonObject(json)) {
1316
- throw new OPE('"response" body must be a top level object');
1521
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1317
1522
  }
1318
1523
  }
1319
1524
  if (typeof json.active !== 'boolean') {
1320
- throw new OPE('"response" body "active" property must be a boolean');
1525
+ throw OPE('"response" body "active" property must be a boolean', INVALID_RESPONSE, {
1526
+ body: json,
1527
+ });
1321
1528
  }
1322
1529
  return json;
1323
1530
  }
1324
1531
  async function jwksRequest(as, options) {
1325
1532
  assertAs(as);
1326
- const url = resolveEndpoint(as, 'jwks_uri');
1533
+ const url = resolveEndpoint(as, 'jwks_uri', false, options?.[allowInsecureRequests] !== true);
1327
1534
  const headers = prepareHeaders(options?.headers);
1328
1535
  headers.set('accept', 'application/json');
1329
1536
  headers.append('accept', 'application/jwk-set+json');
1330
1537
  return (options?.[customFetch] || fetch)(url.href, {
1538
+ body: undefined,
1331
1539
  headers: Object.fromEntries(headers.entries()),
1332
1540
  method: 'GET',
1333
1541
  redirect: 'manual',
1334
- signal: options?.signal ? signal(options.signal) : null,
1335
- }).then(processDpopNonce);
1542
+ signal: options?.signal ? signal(options.signal) : undefined,
1543
+ });
1336
1544
  }
1337
1545
  async function processJwksResponse(response) {
1338
1546
  if (!looseInstanceOf(response, Response)) {
1339
- throw new TypeError('"response" must be an instance of Response');
1547
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1340
1548
  }
1341
1549
  if (response.status !== 200) {
1342
- throw new OPE('"response" is not a conform JSON Web Key Set response');
1550
+ throw OPE('"response" is not a conform JSON Web Key Set response', RESPONSE_IS_NOT_CONFORM, response);
1343
1551
  }
1344
1552
  assertReadableResponse(response);
1553
+ assertContentTypes(response, 'application/json', 'application/jwk-set+json');
1345
1554
  let json;
1346
1555
  try {
1347
1556
  json = await response.json();
1348
1557
  }
1349
1558
  catch (cause) {
1350
- throw new OPE('failed to parse "response" body as JSON', { cause });
1559
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1351
1560
  }
1352
1561
  if (!isJsonObject(json)) {
1353
- throw new OPE('"response" body must be a top level object');
1562
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1354
1563
  }
1355
1564
  if (!Array.isArray(json.keys)) {
1356
- throw new OPE('"response" body "keys" property must be an array');
1565
+ throw OPE('"response" body "keys" property must be an array', INVALID_RESPONSE, { body: json });
1357
1566
  }
1358
1567
  if (!Array.prototype.every.call(json.keys, isJsonObject)) {
1359
- throw new OPE('"response" body "keys" property members must be JWK formatted objects');
1568
+ throw OPE('"response" body "keys" property members must be JWK formatted objects', INVALID_RESPONSE, { body: json });
1360
1569
  }
1361
1570
  return json;
1362
1571
  }
1363
1572
  async function handleOAuthBodyError(response) {
1364
1573
  if (response.status > 399 && response.status < 500) {
1365
1574
  assertReadableResponse(response);
1575
+ assertApplicationJson(response);
1366
1576
  try {
1367
- const json = await response.json();
1577
+ const json = await response.clone().json();
1368
1578
  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
1579
  return json;
1382
1580
  }
1383
1581
  }
@@ -1385,19 +1583,42 @@ async function handleOAuthBodyError(response) {
1385
1583
  }
1386
1584
  return undefined;
1387
1585
  }
1388
- function checkSupportedJwsAlg(alg) {
1389
- if (!SUPPORTED_JWS_ALGS.includes(alg)) {
1390
- throw new UnsupportedOperationError('unsupported JWS "alg" identifier');
1586
+ function supported(alg) {
1587
+ switch (alg) {
1588
+ case 'PS256':
1589
+ case 'ES256':
1590
+ case 'RS256':
1591
+ case 'PS384':
1592
+ case 'ES384':
1593
+ case 'RS384':
1594
+ case 'PS512':
1595
+ case 'ES512':
1596
+ case 'RS512':
1597
+ case 'Ed25519':
1598
+ case 'EdDSA':
1599
+ return true;
1600
+ default:
1601
+ return false;
1391
1602
  }
1392
- return alg;
1393
1603
  }
1394
- function checkRsaKeyAlgorithm(algorithm) {
1604
+ function checkSupportedJwsAlg(header) {
1605
+ if (!supported(header.alg)) {
1606
+ throw new UnsupportedOperationError('unsupported JWS "alg" identifier', {
1607
+ cause: { alg: header.alg },
1608
+ });
1609
+ }
1610
+ }
1611
+ function checkRsaKeyAlgorithm(key) {
1612
+ const { algorithm } = key;
1395
1613
  if (typeof algorithm.modulusLength !== 'number' || algorithm.modulusLength < 2048) {
1396
- throw new OPE(`${algorithm.name} modulusLength must be at least 2048 bits`);
1614
+ throw new UnsupportedOperationError(`unsupported ${algorithm.name} modulusLength`, {
1615
+ cause: key,
1616
+ });
1397
1617
  }
1398
1618
  }
1399
- function ecdsaHashName(namedCurve) {
1400
- switch (namedCurve) {
1619
+ function ecdsaHashName(key) {
1620
+ const { algorithm } = key;
1621
+ switch (algorithm.namedCurve) {
1401
1622
  case 'P-256':
1402
1623
  return 'SHA-256';
1403
1624
  case 'P-384':
@@ -1405,7 +1626,7 @@ function ecdsaHashName(namedCurve) {
1405
1626
  case 'P-521':
1406
1627
  return 'SHA-512';
1407
1628
  default:
1408
- throw new UnsupportedOperationError();
1629
+ throw new UnsupportedOperationError('unsupported ECDSA namedCurve', { cause: key });
1409
1630
  }
1410
1631
  }
1411
1632
  function keyToSubtle(key) {
@@ -1413,10 +1634,10 @@ function keyToSubtle(key) {
1413
1634
  case 'ECDSA':
1414
1635
  return {
1415
1636
  name: key.algorithm.name,
1416
- hash: ecdsaHashName(key.algorithm.namedCurve),
1637
+ hash: ecdsaHashName(key),
1417
1638
  };
1418
1639
  case 'RSA-PSS': {
1419
- checkRsaKeyAlgorithm(key.algorithm);
1640
+ checkRsaKeyAlgorithm(key);
1420
1641
  switch (key.algorithm.hash.name) {
1421
1642
  case 'SHA-256':
1422
1643
  case 'SHA-384':
@@ -1426,103 +1647,109 @@ function keyToSubtle(key) {
1426
1647
  saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3,
1427
1648
  };
1428
1649
  default:
1429
- throw new UnsupportedOperationError();
1650
+ throw new UnsupportedOperationError('unsupported RSA-PSS hash name', { cause: key });
1430
1651
  }
1431
1652
  }
1432
1653
  case 'RSASSA-PKCS1-v1_5':
1433
- checkRsaKeyAlgorithm(key.algorithm);
1654
+ checkRsaKeyAlgorithm(key);
1434
1655
  return key.algorithm.name;
1435
- case 'Ed448':
1436
1656
  case 'Ed25519':
1657
+ case 'EdDSA':
1437
1658
  return key.algorithm.name;
1438
1659
  }
1439
- throw new UnsupportedOperationError();
1660
+ throw new UnsupportedOperationError('unsupported CryptoKey algorithm name', { cause: key });
1440
1661
  }
1441
- const noSignatureCheck = Symbol();
1442
1662
  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));
1663
+ const data = buf(`${protectedHeader}.${payload}`);
1664
+ const algorithm = keyToSubtle(key);
1665
+ const verified = await crypto.subtle.verify(algorithm, key, signature, data);
1445
1666
  if (!verified) {
1446
- throw new OPE('JWT signature verification failed');
1667
+ throw OPE('JWT signature verification failed', INVALID_RESPONSE, {
1668
+ key,
1669
+ data,
1670
+ signature,
1671
+ algorithm,
1672
+ });
1447
1673
  }
1448
1674
  }
1449
- async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance, decryptJwt) {
1450
- let { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.');
1675
+ async function validateJwt(jws, checkAlg, clockSkew, clockTolerance, decryptJwt) {
1676
+ let { 0: protectedHeader, 1: payload, length } = jws.split('.');
1451
1677
  if (length === 5) {
1452
1678
  if (decryptJwt !== undefined) {
1453
1679
  jws = await decryptJwt(jws);
1454
- ({ 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.'));
1680
+ ({ 0: protectedHeader, 1: payload, length } = jws.split('.'));
1455
1681
  }
1456
1682
  else {
1457
- throw new UnsupportedOperationError('JWE structure JWTs are not supported');
1683
+ throw new UnsupportedOperationError('JWE decryption is not configured', { cause: jws });
1458
1684
  }
1459
1685
  }
1460
1686
  if (length !== 3) {
1461
- throw new OPE('Invalid JWT');
1687
+ throw OPE('Invalid JWT', INVALID_RESPONSE, jws);
1462
1688
  }
1463
1689
  let header;
1464
1690
  try {
1465
1691
  header = JSON.parse(buf(b64u(protectedHeader)));
1466
1692
  }
1467
1693
  catch (cause) {
1468
- throw new OPE('failed to parse JWT Header body as base64url encoded JSON', { cause });
1694
+ throw OPE('failed to parse JWT Header body as base64url encoded JSON', PARSE_ERROR, cause);
1469
1695
  }
1470
1696
  if (!isJsonObject(header)) {
1471
- throw new OPE('JWT Header must be a top level object');
1697
+ throw OPE('JWT Header must be a top level object', INVALID_RESPONSE, jws);
1472
1698
  }
1473
1699
  checkAlg(header);
1474
1700
  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);
1701
+ throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
1702
+ cause: { header },
1703
+ });
1482
1704
  }
1483
1705
  let claims;
1484
1706
  try {
1485
1707
  claims = JSON.parse(buf(b64u(payload)));
1486
1708
  }
1487
1709
  catch (cause) {
1488
- throw new OPE('failed to parse JWT Payload body as base64url encoded JSON', { cause });
1710
+ throw OPE('failed to parse JWT Payload body as base64url encoded JSON', PARSE_ERROR, cause);
1489
1711
  }
1490
1712
  if (!isJsonObject(claims)) {
1491
- throw new OPE('JWT Payload must be a top level object');
1713
+ throw OPE('JWT Payload must be a top level object', INVALID_RESPONSE, jws);
1492
1714
  }
1493
1715
  const now = epochTime() + clockSkew;
1494
1716
  if (claims.exp !== undefined) {
1495
1717
  if (typeof claims.exp !== 'number') {
1496
- throw new OPE('unexpected JWT "exp" (expiration time) claim type');
1718
+ throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, { claims });
1497
1719
  }
1498
1720
  if (claims.exp <= now - clockTolerance) {
1499
- throw new OPE('unexpected JWT "exp" (expiration time) claim value, timestamp is <= now()');
1721
+ throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, { claims, now, tolerance: clockTolerance, claim: 'exp' });
1500
1722
  }
1501
1723
  }
1502
1724
  if (claims.iat !== undefined) {
1503
1725
  if (typeof claims.iat !== 'number') {
1504
- throw new OPE('unexpected JWT "iat" (issued at) claim type');
1726
+ throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, { claims });
1505
1727
  }
1506
1728
  }
1507
1729
  if (claims.iss !== undefined) {
1508
1730
  if (typeof claims.iss !== 'string') {
1509
- throw new OPE('unexpected JWT "iss" (issuer) claim type');
1731
+ throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, { claims });
1510
1732
  }
1511
1733
  }
1512
1734
  if (claims.nbf !== undefined) {
1513
1735
  if (typeof claims.nbf !== 'number') {
1514
- throw new OPE('unexpected JWT "nbf" (not before) claim type');
1736
+ throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, { claims });
1515
1737
  }
1516
1738
  if (claims.nbf > now + clockTolerance) {
1517
- throw new OPE('unexpected JWT "nbf" (not before) claim value, timestamp is > now()');
1739
+ throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
1740
+ claims,
1741
+ now,
1742
+ tolerance: clockTolerance,
1743
+ claim: 'nbf',
1744
+ });
1518
1745
  }
1519
1746
  }
1520
1747
  if (claims.aud !== undefined) {
1521
1748
  if (typeof claims.aud !== 'string' && !Array.isArray(claims.aud)) {
1522
- throw new OPE('unexpected JWT "aud" (audience) claim type');
1749
+ throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, { claims });
1523
1750
  }
1524
1751
  }
1525
- return { header, claims, signature, key, jwt: jws };
1752
+ return { header, claims, jwt: jws };
1526
1753
  }
1527
1754
  export async function validateJwtAuthResponse(as, client, parameters, expectedState, options) {
1528
1755
  assertAs(as);
@@ -1531,16 +1758,20 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
1531
1758
  parameters = parameters.searchParams;
1532
1759
  }
1533
1760
  if (!(parameters instanceof URLSearchParams)) {
1534
- throw new TypeError('"parameters" must be an instance of URLSearchParams, or URL');
1761
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
1535
1762
  }
1536
1763
  const response = getURLSearchParameter(parameters, 'response');
1537
1764
  if (!response) {
1538
- throw new OPE('"parameters" does not contain a JARM response');
1765
+ throw OPE('"parameters" does not contain a JARM response', INVALID_RESPONSE);
1539
1766
  }
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])
1767
+ 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
1768
  .then(validatePresence.bind(undefined, ['aud', 'exp', 'iss']))
1542
- .then(validateIssuer.bind(undefined, as.issuer))
1769
+ .then(validateIssuer.bind(undefined, as))
1543
1770
  .then(validateAudience.bind(undefined, client.client_id));
1771
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwt.split('.');
1772
+ const signature = b64u(encodedSignature);
1773
+ const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
1774
+ await validateJwsSignature(protectedHeader, payload, key, signature);
1544
1775
  const result = new URLSearchParams();
1545
1776
  for (const [key, value] of Object.entries(claims)) {
1546
1777
  if (typeof value === 'string' && key !== 'aud') {
@@ -1549,9 +1780,9 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
1549
1780
  }
1550
1781
  return validateAuthResponse(as, client, result, expectedState);
1551
1782
  }
1552
- async function idTokenHash(alg, data, key) {
1783
+ async function idTokenHash(data, header, claimName) {
1553
1784
  let algorithm;
1554
- switch (alg) {
1785
+ switch (header.alg) {
1555
1786
  case 'RS256':
1556
1787
  case 'PS256':
1557
1788
  case 'ES256':
@@ -1565,35 +1796,37 @@ async function idTokenHash(alg, data, key) {
1565
1796
  case 'RS512':
1566
1797
  case 'PS512':
1567
1798
  case 'ES512':
1799
+ case 'Ed25519':
1800
+ case 'EdDSA':
1568
1801
  algorithm = 'SHA-512';
1569
1802
  break;
1570
- case 'EdDSA':
1571
- if (key.algorithm.name === 'Ed25519') {
1572
- algorithm = 'SHA-512';
1573
- break;
1574
- }
1575
- throw new UnsupportedOperationError();
1576
1803
  default:
1577
- throw new UnsupportedOperationError();
1804
+ throw new UnsupportedOperationError(`unsupported JWS algorithm for ${claimName} calculation`, { cause: { alg: header.alg } });
1578
1805
  }
1579
1806
  const digest = await crypto.subtle.digest(algorithm, buf(data));
1580
1807
  return b64u(digest.slice(0, digest.byteLength / 2));
1581
1808
  }
1582
- async function idTokenHashMatches(data, actual, alg, key) {
1583
- const expected = await idTokenHash(alg, data, key);
1809
+ async function idTokenHashMatches(data, actual, header, claimName) {
1810
+ const expected = await idTokenHash(data, header, claimName);
1584
1811
  return actual === expected;
1585
1812
  }
1586
1813
  export async function validateDetachedSignatureResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options) {
1814
+ return validateHybridResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options, true);
1815
+ }
1816
+ export async function validateCodeIdTokenResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options) {
1817
+ return validateHybridResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options, false);
1818
+ }
1819
+ async function validateHybridResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options, fapi) {
1587
1820
  assertAs(as);
1588
1821
  assertClient(client);
1589
1822
  if (parameters instanceof URL) {
1590
1823
  if (!parameters.hash.length) {
1591
- throw new TypeError('"parameters" as an instance of URL must contain a hash (fragment) with the Authorization Response parameters');
1824
+ throw CodedTypeError('"parameters" as an instance of URL must contain a hash (fragment) with the Authorization Response parameters', ERR_INVALID_ARG_VALUE);
1592
1825
  }
1593
1826
  parameters = new URLSearchParams(parameters.hash.slice(1));
1594
1827
  }
1595
1828
  if (!(parameters instanceof URLSearchParams)) {
1596
- throw new TypeError('"parameters" must be an instance of URLSearchParams');
1829
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams', ERR_INVALID_ARG_TYPE);
1597
1830
  }
1598
1831
  parameters = new URLSearchParams(parameters);
1599
1832
  const id_token = getURLSearchParameter(parameters, 'id_token');
@@ -1603,23 +1836,18 @@ export async function validateDetachedSignatureResponse(as, client, parameters,
1603
1836
  case expectNoState:
1604
1837
  break;
1605
1838
  default:
1606
- if (!validateString(expectedState)) {
1607
- throw new TypeError('"expectedState" must be a non-empty string');
1608
- }
1839
+ assertString(expectedState, '"expectedState" argument');
1609
1840
  }
1610
1841
  const result = validateAuthResponse({
1611
1842
  ...as,
1612
1843
  authorization_response_iss_parameter_supported: false,
1613
1844
  }, client, parameters, expectedState);
1614
- if (isOAuth2Error(result)) {
1615
- return result;
1616
- }
1617
1845
  if (!id_token) {
1618
- throw new OPE('"parameters" does not contain an ID Token');
1846
+ throw OPE('"parameters" does not contain an ID Token', INVALID_RESPONSE);
1619
1847
  }
1620
1848
  const code = getURLSearchParameter(parameters, 'code');
1621
1849
  if (!code) {
1622
- throw new OPE('"parameters" does not contain an Authorization Code');
1850
+ throw OPE('"parameters" does not contain an Authorization Code', INVALID_RESPONSE);
1623
1851
  }
1624
1852
  const requiredClaims = [
1625
1853
  'aud',
@@ -1630,86 +1858,132 @@ export async function validateDetachedSignatureResponse(as, client, parameters,
1630
1858
  'nonce',
1631
1859
  'c_hash',
1632
1860
  ];
1633
- if (typeof expectedState === 'string') {
1861
+ const state = parameters.get('state');
1862
+ if (fapi && (typeof expectedState === 'string' || state !== null)) {
1634
1863
  requiredClaims.push('s_hash');
1635
1864
  }
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])
1865
+ if (maxAge !== undefined) {
1866
+ assertNumber(maxAge, false, '"maxAge" argument');
1867
+ }
1868
+ else if (client.default_max_age !== undefined) {
1869
+ assertNumber(client.default_max_age, false, '"client.default_max_age"');
1870
+ }
1871
+ maxAge ??= client.default_max_age ?? skipAuthTimeCheck;
1872
+ if (client.require_auth_time || maxAge !== skipAuthTimeCheck) {
1873
+ requiredClaims.push('auth_time');
1874
+ }
1875
+ 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
1876
  .then(validatePresence.bind(undefined, requiredClaims))
1638
- .then(validateIssuer.bind(undefined, as.issuer))
1877
+ .then(validateIssuer.bind(undefined, as))
1639
1878
  .then(validateAudience.bind(undefined, client.client_id));
1640
1879
  const clockSkew = getClockSkew(client);
1641
1880
  const now = epochTime() + clockSkew;
1642
1881
  if (claims.iat < now - 3600) {
1643
- throw new OPE('unexpected JWT "iat" (issued at) claim value, it is too far in the past');
1882
+ throw OPE('unexpected JWT "iat" (issued at) claim value, it is too far in the past', JWT_TIMESTAMP_CHECK, { now, claims, claim: 'iat' });
1644
1883
  }
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');
1884
+ assertString(claims.c_hash, 'ID Token "c_hash" (code hash) claim value', INVALID_RESPONSE, {
1885
+ claims,
1886
+ });
1887
+ if (claims.auth_time !== undefined) {
1888
+ assertNumber(claims.auth_time, false, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
1665
1889
  }
1666
1890
  if (maxAge !== skipAuthTimeCheck) {
1667
- if (typeof maxAge !== 'number' || maxAge < 0) {
1668
- throw new TypeError('"maxAge" must be a non-negative number');
1669
- }
1670
1891
  const now = epochTime() + getClockSkew(client);
1671
1892
  const tolerance = getClockTolerance(client);
1672
1893
  if (claims.auth_time + maxAge < now - tolerance) {
1673
- throw new OPE('too much time has elapsed since the last End-User authentication');
1894
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: 'auth_time' });
1674
1895
  }
1675
1896
  }
1676
- if (!validateString(expectedNonce)) {
1677
- throw new TypeError('"expectedNonce" must be a non-empty string');
1678
- }
1897
+ assertString(expectedNonce, '"expectedNonce" argument');
1679
1898
  if (claims.nonce !== expectedNonce) {
1680
- throw new OPE('unexpected ID Token "nonce" claim value');
1899
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1900
+ expected: expectedNonce,
1901
+ claims,
1902
+ claim: 'nonce',
1903
+ });
1681
1904
  }
1682
1905
  if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
1683
1906
  if (claims.azp === undefined) {
1684
- throw new OPE('ID Token "aud" (audience) claim includes additional untrusted audiences');
1907
+ throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: 'aud' });
1685
1908
  }
1686
1909
  if (claims.azp !== client.client_id) {
1687
- throw new OPE('unexpected ID Token "azp" (authorized party) claim value');
1910
+ throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
1911
+ expected: client.client_id,
1912
+ claims,
1913
+ claim: 'azp',
1914
+ });
1915
+ }
1916
+ }
1917
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwt.split('.');
1918
+ const signature = b64u(encodedSignature);
1919
+ const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
1920
+ await validateJwsSignature(protectedHeader, payload, key, signature);
1921
+ if ((await idTokenHashMatches(code, claims.c_hash, header, 'c_hash')) !== true) {
1922
+ throw OPE('invalid ID Token "c_hash" (code hash) claim value', JWT_CLAIM_COMPARISON, {
1923
+ code,
1924
+ alg: header.alg,
1925
+ claim: 'c_hash',
1926
+ claims,
1927
+ });
1928
+ }
1929
+ if ((fapi && state !== null) || claims.s_hash !== undefined) {
1930
+ assertString(claims.s_hash, 'ID Token "s_hash" (state hash) claim value', INVALID_RESPONSE, {
1931
+ claims,
1932
+ });
1933
+ assertString(state, '"state" response parameter', INVALID_RESPONSE, { parameters });
1934
+ if ((await idTokenHashMatches(state, claims.s_hash, header, 's_hash')) !== true) {
1935
+ throw OPE('invalid ID Token "s_hash" (state hash) claim value', JWT_CLAIM_COMPARISON, {
1936
+ state,
1937
+ alg: header.alg,
1938
+ claim: 's_hash',
1939
+ claims,
1940
+ });
1688
1941
  }
1689
1942
  }
1690
1943
  return result;
1691
1944
  }
1692
- function checkSigningAlgorithm(client, issuer, header) {
1945
+ const SUPPORTED_JWS_ALGS = Symbol();
1946
+ function checkSigningAlgorithm(client, issuer, fallback, header) {
1693
1947
  if (client !== undefined) {
1694
- if (header.alg !== client) {
1695
- throw new OPE('unexpected JWT "alg" header parameter');
1948
+ if (typeof client === 'string' ? header.alg !== client : !client.includes(header.alg)) {
1949
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1950
+ header,
1951
+ expected: client,
1952
+ reason: 'client configuration',
1953
+ });
1696
1954
  }
1697
1955
  return;
1698
1956
  }
1699
1957
  if (Array.isArray(issuer)) {
1700
1958
  if (!issuer.includes(header.alg)) {
1701
- throw new OPE('unexpected JWT "alg" header parameter');
1959
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1960
+ header,
1961
+ expected: issuer,
1962
+ reason: 'authorization server metadata',
1963
+ });
1702
1964
  }
1703
1965
  return;
1704
1966
  }
1705
- if (header.alg !== 'RS256') {
1706
- throw new OPE('unexpected JWT "alg" header parameter');
1967
+ if (fallback !== undefined) {
1968
+ if (typeof fallback === 'string'
1969
+ ? header.alg !== fallback
1970
+ : typeof fallback === 'symbol'
1971
+ ? !supported(header.alg)
1972
+ : !fallback.includes(header.alg)) {
1973
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1974
+ header,
1975
+ expected: fallback,
1976
+ reason: 'default value',
1977
+ });
1978
+ }
1979
+ return;
1707
1980
  }
1981
+ throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', undefined, { client, issuer, fallback });
1708
1982
  }
1709
1983
  function getURLSearchParameter(parameters, name) {
1710
1984
  const { 0: value, length } = parameters.getAll(name);
1711
1985
  if (length > 1) {
1712
- throw new OPE(`"${name}" parameter must be provided only once`);
1986
+ throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
1713
1987
  }
1714
1988
  return value;
1715
1989
  }
@@ -1722,46 +1996,47 @@ export function validateAuthResponse(as, client, parameters, expectedState) {
1722
1996
  parameters = parameters.searchParams;
1723
1997
  }
1724
1998
  if (!(parameters instanceof URLSearchParams)) {
1725
- throw new TypeError('"parameters" must be an instance of URLSearchParams, or URL');
1999
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
1726
2000
  }
1727
2001
  if (getURLSearchParameter(parameters, 'response')) {
1728
- throw new OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()');
2002
+ throw OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()', INVALID_RESPONSE, { parameters });
1729
2003
  }
1730
2004
  const iss = getURLSearchParameter(parameters, 'iss');
1731
2005
  const state = getURLSearchParameter(parameters, 'state');
1732
2006
  if (!iss && as.authorization_response_iss_parameter_supported) {
1733
- throw new OPE('response parameter "iss" (issuer) missing');
2007
+ throw OPE('response parameter "iss" (issuer) missing', INVALID_RESPONSE, { parameters });
1734
2008
  }
1735
2009
  if (iss && iss !== as.issuer) {
1736
- throw new OPE('unexpected "iss" (issuer) response parameter value');
2010
+ throw OPE('unexpected "iss" (issuer) response parameter value', INVALID_RESPONSE, {
2011
+ expected: as.issuer,
2012
+ parameters,
2013
+ });
1737
2014
  }
1738
2015
  switch (expectedState) {
1739
2016
  case undefined:
1740
2017
  case expectNoState:
1741
2018
  if (state !== undefined) {
1742
- throw new OPE('unexpected "state" response parameter encountered');
2019
+ throw OPE('unexpected "state" response parameter encountered', INVALID_RESPONSE, {
2020
+ expected: undefined,
2021
+ parameters,
2022
+ });
1743
2023
  }
1744
2024
  break;
1745
2025
  case skipStateCheck:
1746
2026
  break;
1747
2027
  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
- }
2028
+ assertString(expectedState, '"expectedState" argument');
1754
2029
  if (state !== expectedState) {
1755
- throw new OPE('unexpected "state" response parameter value');
2030
+ throw OPE(state === undefined
2031
+ ? 'response parameter "state" missing'
2032
+ : 'unexpected "state" response parameter value', INVALID_RESPONSE, { expected: expectedState, parameters });
1756
2033
  }
1757
2034
  }
1758
2035
  const error = getURLSearchParameter(parameters, 'error');
1759
2036
  if (error) {
1760
- return {
1761
- error,
1762
- error_description: getURLSearchParameter(parameters, 'error_description'),
1763
- error_uri: getURLSearchParameter(parameters, 'error_uri'),
1764
- };
2037
+ throw new AuthorizationResponseError('authorization response from the server is an error', {
2038
+ cause: parameters,
2039
+ });
1765
2040
  }
1766
2041
  const id_token = getURLSearchParameter(parameters, 'id_token');
1767
2042
  const token = getURLSearchParameter(parameters, 'token');
@@ -1770,7 +2045,7 @@ export function validateAuthResponse(as, client, parameters, expectedState) {
1770
2045
  }
1771
2046
  return brand(new URLSearchParams(parameters));
1772
2047
  }
1773
- function algToSubtle(alg, crv) {
2048
+ function algToSubtle(alg) {
1774
2049
  switch (alg) {
1775
2050
  case 'PS256':
1776
2051
  case 'PS384':
@@ -1785,96 +2060,94 @@ function algToSubtle(alg, crv) {
1785
2060
  return { name: 'ECDSA', namedCurve: `P-${alg.slice(-3)}` };
1786
2061
  case 'ES512':
1787
2062
  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
- }
2063
+ case 'Ed25519':
2064
+ case 'EdDSA':
2065
+ return 'Ed25519';
1797
2066
  default:
1798
- throw new UnsupportedOperationError();
2067
+ throw new UnsupportedOperationError('unsupported JWS algorithm', { cause: { alg } });
1799
2068
  }
1800
2069
  }
1801
2070
  async function importJwk(alg, jwk) {
1802
2071
  const { ext, key_ops, use, ...key } = jwk;
1803
- return crypto.subtle.importKey('jwk', key, algToSubtle(alg, jwk.crv), true, ['verify']);
2072
+ return crypto.subtle.importKey('jwk', key, algToSubtle(alg), true, ['verify']);
1804
2073
  }
1805
- export async function deviceAuthorizationRequest(as, client, parameters, options) {
2074
+ export async function deviceAuthorizationRequest(as, client, clientAuthentication, parameters, options) {
1806
2075
  assertAs(as);
1807
2076
  assertClient(client);
1808
- const url = resolveEndpoint(as, 'device_authorization_endpoint', alias(client, options));
2077
+ const url = resolveEndpoint(as, 'device_authorization_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1809
2078
  const body = new URLSearchParams(parameters);
1810
2079
  body.set('client_id', client.client_id);
1811
2080
  const headers = prepareHeaders(options?.headers);
1812
2081
  headers.set('accept', 'application/json');
1813
- return authenticatedRequest(as, client, 'POST', url, body, headers, options);
2082
+ return authenticatedRequest(as, client, clientAuthentication, url, body, headers, options);
1814
2083
  }
1815
2084
  export async function processDeviceAuthorizationResponse(as, client, response) {
1816
2085
  assertAs(as);
1817
2086
  assertClient(client);
1818
2087
  if (!looseInstanceOf(response, Response)) {
1819
- throw new TypeError('"response" must be an instance of Response');
2088
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
2089
+ }
2090
+ let challenges;
2091
+ if ((challenges = parseWwwAuthenticateChallenges(response))) {
2092
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', { cause: challenges, response });
1820
2093
  }
1821
2094
  if (response.status !== 200) {
1822
2095
  let err;
1823
2096
  if ((err = await handleOAuthBodyError(response))) {
1824
- return err;
2097
+ await response.body?.cancel();
2098
+ throw new ResponseBodyError('server responded with an error in the response body', {
2099
+ cause: err,
2100
+ response,
2101
+ });
1825
2102
  }
1826
- throw new OPE('"response" is not a conform Device Authorization Endpoint response');
2103
+ throw OPE('"response" is not a conform Device Authorization Endpoint response', RESPONSE_IS_NOT_CONFORM, response);
1827
2104
  }
1828
2105
  assertReadableResponse(response);
2106
+ assertApplicationJson(response);
1829
2107
  let json;
1830
2108
  try {
1831
2109
  json = await response.json();
1832
2110
  }
1833
2111
  catch (cause) {
1834
- throw new OPE('failed to parse "response" body as JSON', { cause });
2112
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1835
2113
  }
1836
2114
  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');
2115
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1844
2116
  }
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');
2117
+ assertString(json.device_code, '"response" body "device_code" property', INVALID_RESPONSE, {
2118
+ body: json,
2119
+ });
2120
+ assertString(json.user_code, '"response" body "user_code" property', INVALID_RESPONSE, {
2121
+ body: json,
2122
+ });
2123
+ assertString(json.verification_uri, '"response" body "verification_uri" property', INVALID_RESPONSE, { body: json });
2124
+ assertNumber(json.expires_in, false, '"response" body "expires_in" property', INVALID_RESPONSE, {
2125
+ body: json,
2126
+ });
2127
+ if (json.verification_uri_complete !== undefined) {
2128
+ assertString(json.verification_uri_complete, '"response" body "verification_uri_complete" property', INVALID_RESPONSE, { body: json });
1854
2129
  }
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');
2130
+ if (json.interval !== undefined) {
2131
+ assertNumber(json.interval, false, '"response" body "interval" property', INVALID_RESPONSE, {
2132
+ body: json,
2133
+ });
1857
2134
  }
1858
2135
  return json;
1859
2136
  }
1860
- export async function deviceCodeGrantRequest(as, client, deviceCode, options) {
2137
+ export async function deviceCodeGrantRequest(as, client, clientAuthentication, deviceCode, options) {
1861
2138
  assertAs(as);
1862
2139
  assertClient(client);
1863
- if (!validateString(deviceCode)) {
1864
- throw new TypeError('"deviceCode" must be a non-empty string');
1865
- }
2140
+ assertString(deviceCode, '"deviceCode"');
1866
2141
  const parameters = new URLSearchParams(options?.additionalParameters);
1867
2142
  parameters.set('device_code', deviceCode);
1868
- return tokenEndpointRequest(as, client, 'urn:ietf:params:oauth:grant-type:device_code', parameters, options);
2143
+ return tokenEndpointRequest(as, client, clientAuthentication, 'urn:ietf:params:oauth:grant-type:device_code', parameters, options);
1869
2144
  }
1870
- export async function processDeviceCodeResponse(as, client, response) {
1871
- return processGenericAccessTokenResponse(as, client, response);
2145
+ export async function processDeviceCodeResponse(as, client, response, options) {
2146
+ return processGenericAccessTokenResponse(as, client, response, undefined, options);
1872
2147
  }
1873
2148
  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);
2149
+ assertString(alg, '"alg"');
2150
+ const algorithm = algToSubtle(alg);
1878
2151
  if (alg.startsWith('PS') || alg.startsWith('RS')) {
1879
2152
  Object.assign(algorithm, {
1880
2153
  modulusLength: options?.modulusLength ?? 2048,
@@ -1892,46 +2165,53 @@ function normalizeHtu(htu) {
1892
2165
  url.hash = '';
1893
2166
  return url.href;
1894
2167
  }
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');
2168
+ async function validateDPoP(request, accessToken, accessTokenClaims, options) {
2169
+ const headerValue = request.headers.get('dpop');
2170
+ if (headerValue === null) {
2171
+ throw OPE('operation indicated DPoP use but the request has no DPoP HTTP Header', INVALID_REQUEST, { headers: request.headers });
1899
2172
  }
1900
2173
  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`);
2174
+ throw OPE(`operation indicated DPoP use but the request's Authorization HTTP Header scheme is not DPoP`, INVALID_REQUEST, { headers: request.headers });
1902
2175
  }
1903
2176
  if (typeof accessTokenClaims.cnf?.jkt !== 'string') {
1904
- throw new OPE('operation indicated DPoP use but the JWT Access Token has no jkt confirmation claim');
2177
+ throw OPE('operation indicated DPoP use but the JWT Access Token has no jkt confirmation claim', INVALID_REQUEST, { claims: accessTokenClaims });
1905
2178
  }
1906
2179
  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)
2180
+ const proof = await validateJwt(headerValue, checkSigningAlgorithm.bind(undefined, options?.signingAlgorithms, undefined, SUPPORTED_JWS_ALGS), clockSkew, getClockTolerance(options), undefined)
1917
2181
  .then(checkJwtType.bind(undefined, 'dpop+jwt'))
1918
2182
  .then(validatePresence.bind(undefined, ['iat', 'jti', 'ath', 'htm', 'htu']));
1919
2183
  const now = epochTime() + clockSkew;
1920
2184
  const diff = Math.abs(now - proof.claims.iat);
1921
2185
  if (diff > 300) {
1922
- throw new OPE('DPoP Proof iat is not recent enough');
2186
+ throw OPE('DPoP Proof iat is not recent enough', JWT_TIMESTAMP_CHECK, {
2187
+ now,
2188
+ claims: proof.claims,
2189
+ claim: 'iat',
2190
+ });
1923
2191
  }
1924
2192
  if (proof.claims.htm !== request.method) {
1925
- throw new OPE('DPoP Proof htm mismatch');
2193
+ throw OPE('DPoP Proof htm mismatch', JWT_CLAIM_COMPARISON, {
2194
+ expected: request.method,
2195
+ claims: proof.claims,
2196
+ claim: 'htm',
2197
+ });
1926
2198
  }
1927
2199
  if (typeof proof.claims.htu !== 'string' ||
1928
2200
  normalizeHtu(proof.claims.htu) !== normalizeHtu(request.url)) {
1929
- throw new OPE('DPoP Proof htu mismatch');
2201
+ throw OPE('DPoP Proof htu mismatch', JWT_CLAIM_COMPARISON, {
2202
+ expected: normalizeHtu(request.url),
2203
+ claims: proof.claims,
2204
+ claim: 'htu',
2205
+ });
1930
2206
  }
1931
2207
  {
1932
- const expected = b64u(await crypto.subtle.digest('SHA-256', encoder.encode(accessToken)));
2208
+ const expected = b64u(await crypto.subtle.digest('SHA-256', buf(accessToken)));
1933
2209
  if (proof.claims.ath !== expected) {
1934
- throw new OPE('DPoP Proof ath mismatch');
2210
+ throw OPE('DPoP Proof ath mismatch', JWT_CLAIM_COMPARISON, {
2211
+ expected,
2212
+ claims: proof.claims,
2213
+ claim: 'ath',
2214
+ });
1935
2215
  }
1936
2216
  }
1937
2217
  {
@@ -1960,25 +2240,44 @@ async function validateDPoP(as, request, accessToken, accessTokenClaims, options
1960
2240
  };
1961
2241
  break;
1962
2242
  default:
1963
- throw new UnsupportedOperationError();
2243
+ throw new UnsupportedOperationError('unsupported JWK key type', { cause: proof.header.jwk });
1964
2244
  }
1965
- const expected = b64u(await crypto.subtle.digest('SHA-256', encoder.encode(JSON.stringify(components))));
2245
+ const expected = b64u(await crypto.subtle.digest('SHA-256', buf(JSON.stringify(components))));
1966
2246
  if (accessTokenClaims.cnf.jkt !== expected) {
1967
- throw new OPE('JWT Access Token confirmation mismatch');
2247
+ throw OPE('JWT Access Token confirmation mismatch', JWT_CLAIM_COMPARISON, {
2248
+ expected,
2249
+ claims: accessTokenClaims,
2250
+ claim: 'cnf.jkt',
2251
+ });
1968
2252
  }
1969
2253
  }
2254
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = headerValue.split('.');
2255
+ const signature = b64u(encodedSignature);
2256
+ const { jwk, alg } = proof.header;
2257
+ if (!jwk) {
2258
+ throw OPE('DPoP Proof is missing the jwk header parameter', INVALID_REQUEST, {
2259
+ header: proof.header,
2260
+ });
2261
+ }
2262
+ const key = await importJwk(alg, jwk);
2263
+ if (key.type !== 'public') {
2264
+ throw OPE('DPoP Proof jwk header parameter must contain a public key', INVALID_REQUEST, {
2265
+ header: proof.header,
2266
+ });
2267
+ }
2268
+ await validateJwsSignature(protectedHeader, payload, key, signature);
1970
2269
  }
1971
2270
  export async function validateJwtAccessToken(as, request, expectedAudience, options) {
1972
2271
  assertAs(as);
1973
2272
  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');
2273
+ throw CodedTypeError('"request" must be an instance of Request', ERR_INVALID_ARG_TYPE);
1978
2274
  }
2275
+ assertString(expectedAudience, '"expectedAudience"');
1979
2276
  const authorization = request.headers.get('authorization');
1980
2277
  if (authorization === null) {
1981
- throw new OPE('"request" is missing an Authorization HTTP Header');
2278
+ throw OPE('"request" is missing an Authorization HTTP Header', INVALID_REQUEST, {
2279
+ headers: request.headers,
2280
+ });
1982
2281
  }
1983
2282
  let { 0: scheme, 1: accessToken, length } = authorization.split(' ');
1984
2283
  scheme = scheme.toLowerCase();
@@ -1987,10 +2286,14 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
1987
2286
  case 'bearer':
1988
2287
  break;
1989
2288
  default:
1990
- throw new UnsupportedOperationError('unsupported Authorization HTTP Header scheme');
2289
+ throw new UnsupportedOperationError('unsupported Authorization HTTP Header scheme', {
2290
+ cause: { headers: request.headers },
2291
+ });
1991
2292
  }
1992
2293
  if (length !== 2) {
1993
- throw new OPE('invalid Authorization HTTP Header format');
2294
+ throw OPE('invalid Authorization HTTP Header format', INVALID_REQUEST, {
2295
+ headers: request.headers,
2296
+ });
1994
2297
  }
1995
2298
  const requiredClaims = [
1996
2299
  'iss',
@@ -2004,43 +2307,54 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
2004
2307
  if (options?.requireDPoP || scheme === 'dpop' || request.headers.has('dpop')) {
2005
2308
  requiredClaims.push('cnf');
2006
2309
  }
2007
- const { claims } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, undefined, SUPPORTED_JWS_ALGS), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(options), getClockTolerance(options), undefined)
2310
+ const { claims, header } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, options?.signingAlgorithms, undefined, SUPPORTED_JWS_ALGS), getClockSkew(options), getClockTolerance(options), undefined)
2008
2311
  .then(checkJwtType.bind(undefined, 'at+jwt'))
2009
2312
  .then(validatePresence.bind(undefined, requiredClaims))
2010
- .then(validateIssuer.bind(undefined, as.issuer))
2011
- .then(validateAudience.bind(undefined, expectedAudience));
2313
+ .then(validateIssuer.bind(undefined, as))
2314
+ .then(validateAudience.bind(undefined, expectedAudience))
2315
+ .catch(reassignRSCode);
2012
2316
  for (const claim of ['client_id', 'jti', 'sub']) {
2013
2317
  if (typeof claims[claim] !== 'string') {
2014
- throw new OPE(`unexpected JWT "${claim}" claim type`);
2318
+ throw OPE(`unexpected JWT "${claim}" claim type`, INVALID_REQUEST, { claims });
2015
2319
  }
2016
2320
  }
2017
2321
  if ('cnf' in claims) {
2018
2322
  if (!isJsonObject(claims.cnf)) {
2019
- throw new OPE('unexpected JWT "cnf" (confirmation) claim value');
2323
+ throw OPE('unexpected JWT "cnf" (confirmation) claim value', INVALID_REQUEST, { claims });
2020
2324
  }
2021
2325
  const { 0: cnf, length } = Object.keys(claims.cnf);
2022
2326
  if (length) {
2023
2327
  if (length !== 1) {
2024
- throw new UnsupportedOperationError('multiple confirmation claims are not supported');
2328
+ throw new UnsupportedOperationError('multiple confirmation claims are not supported', {
2329
+ cause: { claims },
2330
+ });
2025
2331
  }
2026
2332
  if (cnf !== 'jkt') {
2027
- throw new UnsupportedOperationError('unsupported JWT Confirmation method');
2333
+ throw new UnsupportedOperationError('unsupported JWT Confirmation method', {
2334
+ cause: { claims },
2335
+ });
2028
2336
  }
2029
2337
  }
2030
2338
  }
2339
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = accessToken.split('.');
2340
+ const signature = b64u(encodedSignature);
2341
+ const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
2342
+ await validateJwsSignature(protectedHeader, payload, key, signature);
2031
2343
  if (options?.requireDPoP ||
2032
2344
  scheme === 'dpop' ||
2033
2345
  claims.cnf?.jkt !== undefined ||
2034
2346
  request.headers.has('dpop')) {
2035
- await validateDPoP(as, request, accessToken, claims, options);
2347
+ await validateDPoP(request, accessToken, claims, options).catch(reassignRSCode);
2036
2348
  }
2037
2349
  return claims;
2038
2350
  }
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;
2351
+ function reassignRSCode(err) {
2352
+ if (err instanceof OperationProcessingError && err?.code === INVALID_REQUEST) {
2353
+ err.code = INVALID_RESPONSE;
2354
+ }
2355
+ throw err;
2356
+ }
2357
+ export const _nopkce = Symbol();
2358
+ export const _nodiscoverycheck = Symbol();
2359
+ export const _expectedIssuer = Symbol();
2360
+ //# sourceMappingURL=index.js.map