@tachybase/plugin-auth-oidc 0.23.8

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.
Files changed (93) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/README.md +11 -0
  3. package/README.zh-CN.md +38 -0
  4. package/client.d.ts +2 -0
  5. package/client.js +1 -0
  6. package/dist/client/OIDCButton.d.ts +9 -0
  7. package/dist/client/Options.d.ts +2 -0
  8. package/dist/client/index.d.ts +5 -0
  9. package/dist/client/index.js +3 -0
  10. package/dist/client/locale/index.d.ts +3 -0
  11. package/dist/constants.d.ts +3 -0
  12. package/dist/constants.js +34 -0
  13. package/dist/externalVersion.js +14 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +39 -0
  16. package/dist/locale/en-US.json +40 -0
  17. package/dist/locale/es-ES.json +25 -0
  18. package/dist/locale/fr-FR.json +21 -0
  19. package/dist/locale/ko_KR.json +28 -0
  20. package/dist/locale/pt-BR.json +21 -0
  21. package/dist/locale/zh-CN.json +28 -0
  22. package/dist/node_modules/nanoid/.devcontainer.json +23 -0
  23. package/dist/node_modules/nanoid/LICENSE +20 -0
  24. package/dist/node_modules/nanoid/async/index.browser.cjs +69 -0
  25. package/dist/node_modules/nanoid/async/index.browser.js +69 -0
  26. package/dist/node_modules/nanoid/async/index.cjs +71 -0
  27. package/dist/node_modules/nanoid/async/index.d.ts +56 -0
  28. package/dist/node_modules/nanoid/async/index.js +71 -0
  29. package/dist/node_modules/nanoid/async/index.native.js +57 -0
  30. package/dist/node_modules/nanoid/async/package.json +12 -0
  31. package/dist/node_modules/nanoid/bin/nanoid.cjs +55 -0
  32. package/dist/node_modules/nanoid/index.browser.cjs +72 -0
  33. package/dist/node_modules/nanoid/index.browser.js +72 -0
  34. package/dist/node_modules/nanoid/index.cjs +1 -0
  35. package/dist/node_modules/nanoid/index.d.cts +91 -0
  36. package/dist/node_modules/nanoid/index.d.ts +91 -0
  37. package/dist/node_modules/nanoid/index.js +85 -0
  38. package/dist/node_modules/nanoid/nanoid.js +1 -0
  39. package/dist/node_modules/nanoid/non-secure/index.cjs +34 -0
  40. package/dist/node_modules/nanoid/non-secure/index.d.ts +33 -0
  41. package/dist/node_modules/nanoid/non-secure/index.js +34 -0
  42. package/dist/node_modules/nanoid/non-secure/package.json +6 -0
  43. package/dist/node_modules/nanoid/package.json +1 -0
  44. package/dist/node_modules/nanoid/url-alphabet/index.cjs +7 -0
  45. package/dist/node_modules/nanoid/url-alphabet/index.js +7 -0
  46. package/dist/node_modules/nanoid/url-alphabet/package.json +6 -0
  47. package/dist/node_modules/openid-client/lib/client.js +1884 -0
  48. package/dist/node_modules/openid-client/lib/device_flow_handle.js +125 -0
  49. package/dist/node_modules/openid-client/lib/errors.js +55 -0
  50. package/dist/node_modules/openid-client/lib/helpers/assert.js +24 -0
  51. package/dist/node_modules/openid-client/lib/helpers/base64url.js +13 -0
  52. package/dist/node_modules/openid-client/lib/helpers/client.js +208 -0
  53. package/dist/node_modules/openid-client/lib/helpers/consts.js +7 -0
  54. package/dist/node_modules/openid-client/lib/helpers/decode_jwt.js +27 -0
  55. package/dist/node_modules/openid-client/lib/helpers/deep_clone.js +1 -0
  56. package/dist/node_modules/openid-client/lib/helpers/defaults.js +27 -0
  57. package/dist/node_modules/openid-client/lib/helpers/generators.js +14 -0
  58. package/dist/node_modules/openid-client/lib/helpers/is_key_object.js +4 -0
  59. package/dist/node_modules/openid-client/lib/helpers/is_plain_object.js +1 -0
  60. package/dist/node_modules/openid-client/lib/helpers/issuer.js +111 -0
  61. package/dist/node_modules/openid-client/lib/helpers/keystore.js +298 -0
  62. package/dist/node_modules/openid-client/lib/helpers/merge.js +24 -0
  63. package/dist/node_modules/openid-client/lib/helpers/pick.js +9 -0
  64. package/dist/node_modules/openid-client/lib/helpers/process_response.js +71 -0
  65. package/dist/node_modules/openid-client/lib/helpers/request.js +200 -0
  66. package/dist/node_modules/openid-client/lib/helpers/unix_timestamp.js +1 -0
  67. package/dist/node_modules/openid-client/lib/helpers/weak_cache.js +1 -0
  68. package/dist/node_modules/openid-client/lib/helpers/webfinger_normalize.js +71 -0
  69. package/dist/node_modules/openid-client/lib/helpers/www_authenticate_parser.js +14 -0
  70. package/dist/node_modules/openid-client/lib/index.js +1 -0
  71. package/dist/node_modules/openid-client/lib/issuer.js +192 -0
  72. package/dist/node_modules/openid-client/lib/issuer_registry.js +3 -0
  73. package/dist/node_modules/openid-client/lib/passport_strategy.js +205 -0
  74. package/dist/node_modules/openid-client/lib/token_set.js +35 -0
  75. package/dist/node_modules/openid-client/package.json +1 -0
  76. package/dist/node_modules/openid-client/types/index.d.ts +623 -0
  77. package/dist/server/actions/getAuthUrl.d.ts +2 -0
  78. package/dist/server/actions/getAuthUrl.js +47 -0
  79. package/dist/server/actions/redirect.d.ts +2 -0
  80. package/dist/server/actions/redirect.js +55 -0
  81. package/dist/server/index.d.ts +1 -0
  82. package/dist/server/index.js +33 -0
  83. package/dist/server/migrations/20231007124508-update-autosignup.d.ts +6 -0
  84. package/dist/server/migrations/20231007124508-update-autosignup.js +52 -0
  85. package/dist/server/oidc-auth.d.ts +15 -0
  86. package/dist/server/oidc-auth.js +154 -0
  87. package/dist/server/plugin.d.ts +11 -0
  88. package/dist/server/plugin.js +83 -0
  89. package/dist/swagger/index.d.ts +143 -0
  90. package/dist/swagger/index.js +178 -0
  91. package/package.json +37 -0
  92. package/server.d.ts +2 -0
  93. package/server.js +1 -0
@@ -0,0 +1,1884 @@
1
+ const { inspect } = require('util');
2
+ const stdhttp = require('http');
3
+ const crypto = require('crypto');
4
+ const { strict: assert } = require('assert');
5
+ const querystring = require('querystring');
6
+ const url = require('url');
7
+ const { URL, URLSearchParams } = require('url');
8
+
9
+ const jose = require('jose');
10
+ const tokenHash = require('oidc-token-hash');
11
+
12
+ const isKeyObject = require('./helpers/is_key_object');
13
+ const decodeJWT = require('./helpers/decode_jwt');
14
+ const base64url = require('./helpers/base64url');
15
+ const defaults = require('./helpers/defaults');
16
+ const parseWwwAuthenticate = require('./helpers/www_authenticate_parser');
17
+ const { assertSigningAlgValuesSupport, assertIssuerConfiguration } = require('./helpers/assert');
18
+ const pick = require('./helpers/pick');
19
+ const isPlainObject = require('./helpers/is_plain_object');
20
+ const processResponse = require('./helpers/process_response');
21
+ const TokenSet = require('./token_set');
22
+ const { OPError, RPError } = require('./errors');
23
+ const now = require('./helpers/unix_timestamp');
24
+ const { random } = require('./helpers/generators');
25
+ const request = require('./helpers/request');
26
+ const { CLOCK_TOLERANCE } = require('./helpers/consts');
27
+ const { keystores } = require('./helpers/weak_cache');
28
+ const KeyStore = require('./helpers/keystore');
29
+ const clone = require('./helpers/deep_clone');
30
+ const { authenticatedPost, resolveResponseType, resolveRedirectUri } = require('./helpers/client');
31
+ const { queryKeyStore } = require('./helpers/issuer');
32
+ const DeviceFlowHandle = require('./device_flow_handle');
33
+
34
+ const [major, minor] = process.version
35
+ .slice(1)
36
+ .split('.')
37
+ .map((str) => parseInt(str, 10));
38
+
39
+ const rsaPssParams = major >= 17 || (major === 16 && minor >= 9);
40
+ const retryAttempt = Symbol();
41
+ const skipNonceCheck = Symbol();
42
+ const skipMaxAgeCheck = Symbol();
43
+
44
+ function pickCb(input) {
45
+ return pick(
46
+ input,
47
+ 'access_token', // OAuth 2.0
48
+ 'code', // OAuth 2.0
49
+ 'error_description', // OAuth 2.0
50
+ 'error_uri', // OAuth 2.0
51
+ 'error', // OAuth 2.0
52
+ 'expires_in', // OAuth 2.0
53
+ 'id_token', // OIDC Core 1.0
54
+ 'iss', // draft-ietf-oauth-iss-auth-resp
55
+ 'response', // FAPI JARM
56
+ 'session_state', // OIDC Session Management
57
+ 'state', // OAuth 2.0
58
+ 'token_type', // OAuth 2.0
59
+ );
60
+ }
61
+
62
+ function authorizationHeaderValue(token, tokenType = 'Bearer') {
63
+ return `${tokenType} ${token}`;
64
+ }
65
+
66
+ function getSearchParams(input) {
67
+ const parsed = url.parse(input);
68
+ if (!parsed.search) return {};
69
+ return querystring.parse(parsed.search.substring(1));
70
+ }
71
+
72
+ function verifyPresence(payload, jwt, prop) {
73
+ if (payload[prop] === undefined) {
74
+ throw new RPError({
75
+ message: `missing required JWT property ${prop}`,
76
+ jwt,
77
+ });
78
+ }
79
+ }
80
+
81
+ function authorizationParams(params) {
82
+ const authParams = {
83
+ client_id: this.client_id,
84
+ scope: 'openid',
85
+ response_type: resolveResponseType.call(this),
86
+ redirect_uri: resolveRedirectUri.call(this),
87
+ ...params,
88
+ };
89
+
90
+ Object.entries(authParams).forEach(([key, value]) => {
91
+ if (value === null || value === undefined) {
92
+ delete authParams[key];
93
+ } else if (key === 'claims' && typeof value === 'object') {
94
+ authParams[key] = JSON.stringify(value);
95
+ } else if (key === 'resource' && Array.isArray(value)) {
96
+ authParams[key] = value;
97
+ } else if (typeof value !== 'string') {
98
+ authParams[key] = String(value);
99
+ }
100
+ });
101
+
102
+ return authParams;
103
+ }
104
+
105
+ function getKeystore(jwks) {
106
+ if (
107
+ !isPlainObject(jwks) ||
108
+ !Array.isArray(jwks.keys) ||
109
+ jwks.keys.some((k) => !isPlainObject(k) || !('kty' in k))
110
+ ) {
111
+ throw new TypeError('jwks must be a JSON Web Key Set formatted object');
112
+ }
113
+
114
+ return KeyStore.fromJWKS(jwks, { onlyPrivate: true });
115
+ }
116
+
117
+ // if an OP doesnt support client_secret_basic but supports client_secret_post, use it instead
118
+ // this is in place to take care of most common pitfalls when first using discovered Issuers without
119
+ // the support for default values defined by Discovery 1.0
120
+ function checkBasicSupport(client, properties) {
121
+ try {
122
+ const supported = client.issuer.token_endpoint_auth_methods_supported;
123
+ if (!supported.includes(properties.token_endpoint_auth_method)) {
124
+ if (supported.includes('client_secret_post')) {
125
+ properties.token_endpoint_auth_method = 'client_secret_post';
126
+ }
127
+ }
128
+ } catch (err) {}
129
+ }
130
+
131
+ function handleCommonMistakes(client, metadata, properties) {
132
+ if (!metadata.token_endpoint_auth_method) {
133
+ // if no explicit value was provided
134
+ checkBasicSupport(client, properties);
135
+ }
136
+
137
+ // :fp: c'mon people... RTFM
138
+ if (metadata.redirect_uri) {
139
+ if (metadata.redirect_uris) {
140
+ throw new TypeError('provide a redirect_uri or redirect_uris, not both');
141
+ }
142
+ properties.redirect_uris = [metadata.redirect_uri];
143
+ delete properties.redirect_uri;
144
+ }
145
+
146
+ if (metadata.response_type) {
147
+ if (metadata.response_types) {
148
+ throw new TypeError('provide a response_type or response_types, not both');
149
+ }
150
+ properties.response_types = [metadata.response_type];
151
+ delete properties.response_type;
152
+ }
153
+ }
154
+
155
+ function getDefaultsForEndpoint(endpoint, issuer, properties) {
156
+ if (!issuer[`${endpoint}_endpoint`]) return;
157
+
158
+ const tokenEndpointAuthMethod = properties.token_endpoint_auth_method;
159
+ const tokenEndpointAuthSigningAlg = properties.token_endpoint_auth_signing_alg;
160
+
161
+ const eam = `${endpoint}_endpoint_auth_method`;
162
+ const easa = `${endpoint}_endpoint_auth_signing_alg`;
163
+
164
+ if (properties[eam] === undefined && properties[easa] === undefined) {
165
+ if (tokenEndpointAuthMethod !== undefined) {
166
+ properties[eam] = tokenEndpointAuthMethod;
167
+ }
168
+ if (tokenEndpointAuthSigningAlg !== undefined) {
169
+ properties[easa] = tokenEndpointAuthSigningAlg;
170
+ }
171
+ }
172
+ }
173
+
174
+ class BaseClient {
175
+ #metadata;
176
+ #issuer;
177
+ #aadIssValidation;
178
+ #additionalAuthorizedParties;
179
+ constructor(issuer, aadIssValidation, metadata = {}, jwks, options) {
180
+ this.#metadata = new Map();
181
+ this.#issuer = issuer;
182
+ this.#aadIssValidation = aadIssValidation;
183
+
184
+ if (typeof metadata.client_id !== 'string' || !metadata.client_id) {
185
+ throw new TypeError('client_id is required');
186
+ }
187
+
188
+ const properties = {
189
+ grant_types: ['authorization_code'],
190
+ id_token_signed_response_alg: 'RS256',
191
+ authorization_signed_response_alg: 'RS256',
192
+ response_types: ['code'],
193
+ token_endpoint_auth_method: 'client_secret_basic',
194
+ ...(this.fapi1()
195
+ ? {
196
+ grant_types: ['authorization_code', 'implicit'],
197
+ id_token_signed_response_alg: 'PS256',
198
+ authorization_signed_response_alg: 'PS256',
199
+ response_types: ['code id_token'],
200
+ tls_client_certificate_bound_access_tokens: true,
201
+ token_endpoint_auth_method: undefined,
202
+ }
203
+ : undefined),
204
+ ...(this.fapi2()
205
+ ? {
206
+ id_token_signed_response_alg: 'PS256',
207
+ authorization_signed_response_alg: 'PS256',
208
+ token_endpoint_auth_method: undefined,
209
+ }
210
+ : undefined),
211
+ ...metadata,
212
+ };
213
+
214
+ if (this.fapi()) {
215
+ switch (properties.token_endpoint_auth_method) {
216
+ case 'self_signed_tls_client_auth':
217
+ case 'tls_client_auth':
218
+ break;
219
+ case 'private_key_jwt':
220
+ if (!jwks) {
221
+ throw new TypeError('jwks is required');
222
+ }
223
+ break;
224
+ case undefined:
225
+ throw new TypeError('token_endpoint_auth_method is required');
226
+ default:
227
+ throw new TypeError('invalid or unsupported token_endpoint_auth_method');
228
+ }
229
+ }
230
+
231
+ if (this.fapi2()) {
232
+ if (
233
+ properties.tls_client_certificate_bound_access_tokens &&
234
+ properties.dpop_bound_access_tokens
235
+ ) {
236
+ throw new TypeError(
237
+ 'either tls_client_certificate_bound_access_tokens or dpop_bound_access_tokens must be set to true',
238
+ );
239
+ }
240
+
241
+ if (
242
+ !properties.tls_client_certificate_bound_access_tokens &&
243
+ !properties.dpop_bound_access_tokens
244
+ ) {
245
+ throw new TypeError(
246
+ 'either tls_client_certificate_bound_access_tokens or dpop_bound_access_tokens must be set to true',
247
+ );
248
+ }
249
+ }
250
+
251
+ handleCommonMistakes(this, metadata, properties);
252
+
253
+ assertSigningAlgValuesSupport('token', this.issuer, properties);
254
+ ['introspection', 'revocation'].forEach((endpoint) => {
255
+ getDefaultsForEndpoint(endpoint, this.issuer, properties);
256
+ assertSigningAlgValuesSupport(endpoint, this.issuer, properties);
257
+ });
258
+
259
+ Object.entries(properties).forEach(([key, value]) => {
260
+ this.#metadata.set(key, value);
261
+ if (!this[key]) {
262
+ Object.defineProperty(this, key, {
263
+ get() {
264
+ return this.#metadata.get(key);
265
+ },
266
+ enumerable: true,
267
+ });
268
+ }
269
+ });
270
+
271
+ if (jwks !== undefined) {
272
+ const keystore = getKeystore.call(this, jwks);
273
+ keystores.set(this, keystore);
274
+ }
275
+
276
+ if (options != null && options.additionalAuthorizedParties) {
277
+ this.#additionalAuthorizedParties = clone(options.additionalAuthorizedParties);
278
+ }
279
+
280
+ this[CLOCK_TOLERANCE] = 0;
281
+ }
282
+
283
+ authorizationUrl(params = {}) {
284
+ if (!isPlainObject(params)) {
285
+ throw new TypeError('params must be a plain object');
286
+ }
287
+ assertIssuerConfiguration(this.issuer, 'authorization_endpoint');
288
+ const target = new URL(this.issuer.authorization_endpoint);
289
+
290
+ for (const [name, value] of Object.entries(authorizationParams.call(this, params))) {
291
+ if (Array.isArray(value)) {
292
+ target.searchParams.delete(name);
293
+ for (const member of value) {
294
+ target.searchParams.append(name, member);
295
+ }
296
+ } else {
297
+ target.searchParams.set(name, value);
298
+ }
299
+ }
300
+
301
+ // TODO: is the replace needed?
302
+ return target.href.replace(/\+/g, '%20');
303
+ }
304
+
305
+ authorizationPost(params = {}) {
306
+ if (!isPlainObject(params)) {
307
+ throw new TypeError('params must be a plain object');
308
+ }
309
+ const inputs = authorizationParams.call(this, params);
310
+ const formInputs = Object.keys(inputs)
311
+ .map((name) => `<input type="hidden" name="${name}" value="${inputs[name]}"/>`)
312
+ .join('\n');
313
+
314
+ return `<!DOCTYPE html>
315
+ <head>
316
+ <title>Requesting Authorization</title>
317
+ </head>
318
+ <body onload="javascript:document.forms[0].submit()">
319
+ <form method="post" action="${this.issuer.authorization_endpoint}">
320
+ ${formInputs}
321
+ </form>
322
+ </body>
323
+ </html>`;
324
+ }
325
+
326
+ endSessionUrl(params = {}) {
327
+ assertIssuerConfiguration(this.issuer, 'end_session_endpoint');
328
+
329
+ const { 0: postLogout, length } = this.post_logout_redirect_uris || [];
330
+
331
+ const { post_logout_redirect_uri = length === 1 ? postLogout : undefined } = params;
332
+
333
+ let id_token_hint;
334
+ ({ id_token_hint, ...params } = params);
335
+ if (id_token_hint instanceof TokenSet) {
336
+ if (!id_token_hint.id_token) {
337
+ throw new TypeError('id_token not present in TokenSet');
338
+ }
339
+ id_token_hint = id_token_hint.id_token;
340
+ }
341
+
342
+ const target = url.parse(this.issuer.end_session_endpoint);
343
+ const query = defaults(
344
+ getSearchParams(this.issuer.end_session_endpoint),
345
+ params,
346
+ {
347
+ post_logout_redirect_uri,
348
+ client_id: this.client_id,
349
+ },
350
+ { id_token_hint },
351
+ );
352
+
353
+ Object.entries(query).forEach(([key, value]) => {
354
+ if (value === null || value === undefined) {
355
+ delete query[key];
356
+ }
357
+ });
358
+
359
+ target.search = null;
360
+ target.query = query;
361
+
362
+ return url.format(target);
363
+ }
364
+
365
+ callbackParams(input) {
366
+ const isIncomingMessage =
367
+ input instanceof stdhttp.IncomingMessage || (input && input.method && input.url);
368
+ const isString = typeof input === 'string';
369
+
370
+ if (!isString && !isIncomingMessage) {
371
+ throw new TypeError(
372
+ '#callbackParams only accepts string urls, http.IncomingMessage or a lookalike',
373
+ );
374
+ }
375
+ if (isIncomingMessage) {
376
+ switch (input.method) {
377
+ case 'GET':
378
+ return pickCb(getSearchParams(input.url));
379
+ case 'POST':
380
+ if (input.body === undefined) {
381
+ throw new TypeError(
382
+ 'incoming message body missing, include a body parser prior to this method call',
383
+ );
384
+ }
385
+ switch (typeof input.body) {
386
+ case 'object':
387
+ case 'string':
388
+ if (Buffer.isBuffer(input.body)) {
389
+ return pickCb(querystring.parse(input.body.toString('utf-8')));
390
+ }
391
+ if (typeof input.body === 'string') {
392
+ return pickCb(querystring.parse(input.body));
393
+ }
394
+
395
+ return pickCb(input.body);
396
+ default:
397
+ throw new TypeError('invalid IncomingMessage body object');
398
+ }
399
+ default:
400
+ throw new TypeError('invalid IncomingMessage method');
401
+ }
402
+ } else {
403
+ return pickCb(getSearchParams(input));
404
+ }
405
+ }
406
+
407
+ async callback(
408
+ redirectUri,
409
+ parameters,
410
+ checks = {},
411
+ { exchangeBody, clientAssertionPayload, DPoP } = {},
412
+ ) {
413
+ let params = pickCb(parameters);
414
+
415
+ if (checks.jarm && !('response' in parameters)) {
416
+ throw new RPError({
417
+ message: 'expected a JARM response',
418
+ checks,
419
+ params,
420
+ });
421
+ } else if ('response' in parameters) {
422
+ const decrypted = await this.decryptJARM(params.response);
423
+ params = await this.validateJARM(decrypted);
424
+ }
425
+
426
+ if (this.default_max_age && !checks.max_age) {
427
+ checks.max_age = this.default_max_age;
428
+ }
429
+
430
+ if (params.state && !checks.state) {
431
+ throw new TypeError('checks.state argument is missing');
432
+ }
433
+
434
+ if (!params.state && checks.state) {
435
+ throw new RPError({
436
+ message: 'state missing from the response',
437
+ checks,
438
+ params,
439
+ });
440
+ }
441
+
442
+ if (checks.state !== params.state) {
443
+ throw new RPError({
444
+ printf: ['state mismatch, expected %s, got: %s', checks.state, params.state],
445
+ checks,
446
+ params,
447
+ });
448
+ }
449
+
450
+ if ('iss' in params) {
451
+ assertIssuerConfiguration(this.issuer, 'issuer');
452
+ if (params.iss !== this.issuer.issuer) {
453
+ throw new RPError({
454
+ printf: ['iss mismatch, expected %s, got: %s', this.issuer.issuer, params.iss],
455
+ params,
456
+ });
457
+ }
458
+ } else if (
459
+ this.issuer.authorization_response_iss_parameter_supported &&
460
+ !('id_token' in params) &&
461
+ !('response' in parameters)
462
+ ) {
463
+ throw new RPError({
464
+ message: 'iss missing from the response',
465
+ params,
466
+ });
467
+ }
468
+
469
+ if (params.error) {
470
+ throw new OPError(params);
471
+ }
472
+
473
+ const RESPONSE_TYPE_REQUIRED_PARAMS = {
474
+ code: ['code'],
475
+ id_token: ['id_token'],
476
+ token: ['access_token', 'token_type'],
477
+ };
478
+
479
+ if (checks.response_type) {
480
+ for (const type of checks.response_type.split(' ')) {
481
+ if (type === 'none') {
482
+ if (params.code || params.id_token || params.access_token) {
483
+ throw new RPError({
484
+ message: 'unexpected params encountered for "none" response',
485
+ checks,
486
+ params,
487
+ });
488
+ }
489
+ } else {
490
+ for (const param of RESPONSE_TYPE_REQUIRED_PARAMS[type]) {
491
+ if (!params[param]) {
492
+ throw new RPError({
493
+ message: `${param} missing from response`,
494
+ checks,
495
+ params,
496
+ });
497
+ }
498
+ }
499
+ }
500
+ }
501
+ }
502
+
503
+ if (params.id_token) {
504
+ const tokenset = new TokenSet(params);
505
+ await this.decryptIdToken(tokenset);
506
+ await this.validateIdToken(
507
+ tokenset,
508
+ checks.nonce,
509
+ 'authorization',
510
+ checks.max_age,
511
+ checks.state,
512
+ );
513
+
514
+ if (!params.code) {
515
+ return tokenset;
516
+ }
517
+ }
518
+
519
+ if (params.code) {
520
+ const tokenset = await this.grant(
521
+ {
522
+ ...exchangeBody,
523
+ grant_type: 'authorization_code',
524
+ code: params.code,
525
+ redirect_uri: redirectUri,
526
+ code_verifier: checks.code_verifier,
527
+ },
528
+ { clientAssertionPayload, DPoP },
529
+ );
530
+
531
+ await this.decryptIdToken(tokenset);
532
+ await this.validateIdToken(tokenset, checks.nonce, 'token', checks.max_age);
533
+
534
+ if (params.session_state) {
535
+ tokenset.session_state = params.session_state;
536
+ }
537
+
538
+ return tokenset;
539
+ }
540
+
541
+ return new TokenSet(params);
542
+ }
543
+
544
+ async oauthCallback(
545
+ redirectUri,
546
+ parameters,
547
+ checks = {},
548
+ { exchangeBody, clientAssertionPayload, DPoP } = {},
549
+ ) {
550
+ let params = pickCb(parameters);
551
+
552
+ if (checks.jarm && !('response' in parameters)) {
553
+ throw new RPError({
554
+ message: 'expected a JARM response',
555
+ checks,
556
+ params,
557
+ });
558
+ } else if ('response' in parameters) {
559
+ const decrypted = await this.decryptJARM(params.response);
560
+ params = await this.validateJARM(decrypted);
561
+ }
562
+
563
+ if (params.state && !checks.state) {
564
+ throw new TypeError('checks.state argument is missing');
565
+ }
566
+
567
+ if (!params.state && checks.state) {
568
+ throw new RPError({
569
+ message: 'state missing from the response',
570
+ checks,
571
+ params,
572
+ });
573
+ }
574
+
575
+ if (checks.state !== params.state) {
576
+ throw new RPError({
577
+ printf: ['state mismatch, expected %s, got: %s', checks.state, params.state],
578
+ checks,
579
+ params,
580
+ });
581
+ }
582
+
583
+ if ('iss' in params) {
584
+ assertIssuerConfiguration(this.issuer, 'issuer');
585
+ if (params.iss !== this.issuer.issuer) {
586
+ throw new RPError({
587
+ printf: ['iss mismatch, expected %s, got: %s', this.issuer.issuer, params.iss],
588
+ params,
589
+ });
590
+ }
591
+ } else if (
592
+ this.issuer.authorization_response_iss_parameter_supported &&
593
+ !('id_token' in params) &&
594
+ !('response' in parameters)
595
+ ) {
596
+ throw new RPError({
597
+ message: 'iss missing from the response',
598
+ params,
599
+ });
600
+ }
601
+
602
+ if (params.error) {
603
+ throw new OPError(params);
604
+ }
605
+
606
+ if (typeof params.id_token === 'string' && params.id_token.length) {
607
+ throw new RPError({
608
+ message:
609
+ 'id_token detected in the response, you must use client.callback() instead of client.oauthCallback()',
610
+ params,
611
+ });
612
+ }
613
+ delete params.id_token;
614
+
615
+ const RESPONSE_TYPE_REQUIRED_PARAMS = {
616
+ code: ['code'],
617
+ token: ['access_token', 'token_type'],
618
+ };
619
+
620
+ if (checks.response_type) {
621
+ for (const type of checks.response_type.split(' ')) {
622
+ if (type === 'none') {
623
+ if (params.code || params.id_token || params.access_token) {
624
+ throw new RPError({
625
+ message: 'unexpected params encountered for "none" response',
626
+ checks,
627
+ params,
628
+ });
629
+ }
630
+ }
631
+
632
+ if (RESPONSE_TYPE_REQUIRED_PARAMS[type]) {
633
+ for (const param of RESPONSE_TYPE_REQUIRED_PARAMS[type]) {
634
+ if (!params[param]) {
635
+ throw new RPError({
636
+ message: `${param} missing from response`,
637
+ checks,
638
+ params,
639
+ });
640
+ }
641
+ }
642
+ }
643
+ }
644
+ }
645
+
646
+ if (params.code) {
647
+ const tokenset = await this.grant(
648
+ {
649
+ ...exchangeBody,
650
+ grant_type: 'authorization_code',
651
+ code: params.code,
652
+ redirect_uri: redirectUri,
653
+ code_verifier: checks.code_verifier,
654
+ },
655
+ { clientAssertionPayload, DPoP },
656
+ );
657
+
658
+ if (typeof tokenset.id_token === 'string' && tokenset.id_token.length) {
659
+ throw new RPError({
660
+ message:
661
+ 'id_token detected in the response, you must use client.callback() instead of client.oauthCallback()',
662
+ params,
663
+ });
664
+ }
665
+ delete tokenset.id_token;
666
+
667
+ return tokenset;
668
+ }
669
+
670
+ return new TokenSet(params);
671
+ }
672
+
673
+ async decryptIdToken(token) {
674
+ if (!this.id_token_encrypted_response_alg) {
675
+ return token;
676
+ }
677
+
678
+ let idToken = token;
679
+
680
+ if (idToken instanceof TokenSet) {
681
+ if (!idToken.id_token) {
682
+ throw new TypeError('id_token not present in TokenSet');
683
+ }
684
+ idToken = idToken.id_token;
685
+ }
686
+
687
+ const expectedAlg = this.id_token_encrypted_response_alg;
688
+ const expectedEnc = this.id_token_encrypted_response_enc;
689
+
690
+ const result = await this.decryptJWE(idToken, expectedAlg, expectedEnc);
691
+
692
+ if (token instanceof TokenSet) {
693
+ token.id_token = result;
694
+ return token;
695
+ }
696
+
697
+ return result;
698
+ }
699
+
700
+ async validateJWTUserinfo(body) {
701
+ const expectedAlg = this.userinfo_signed_response_alg;
702
+
703
+ return this.validateJWT(body, expectedAlg, []);
704
+ }
705
+
706
+ async decryptJARM(response) {
707
+ if (!this.authorization_encrypted_response_alg) {
708
+ return response;
709
+ }
710
+
711
+ const expectedAlg = this.authorization_encrypted_response_alg;
712
+ const expectedEnc = this.authorization_encrypted_response_enc;
713
+
714
+ return this.decryptJWE(response, expectedAlg, expectedEnc);
715
+ }
716
+
717
+ async decryptJWTUserinfo(body) {
718
+ if (!this.userinfo_encrypted_response_alg) {
719
+ return body;
720
+ }
721
+
722
+ const expectedAlg = this.userinfo_encrypted_response_alg;
723
+ const expectedEnc = this.userinfo_encrypted_response_enc;
724
+
725
+ return this.decryptJWE(body, expectedAlg, expectedEnc);
726
+ }
727
+
728
+ async decryptJWE(jwe, expectedAlg, expectedEnc = 'A128CBC-HS256') {
729
+ const header = JSON.parse(base64url.decode(jwe.split('.')[0]));
730
+
731
+ if (header.alg !== expectedAlg) {
732
+ throw new RPError({
733
+ printf: ['unexpected JWE alg received, expected %s, got: %s', expectedAlg, header.alg],
734
+ jwt: jwe,
735
+ });
736
+ }
737
+
738
+ if (header.enc !== expectedEnc) {
739
+ throw new RPError({
740
+ printf: ['unexpected JWE enc received, expected %s, got: %s', expectedEnc, header.enc],
741
+ jwt: jwe,
742
+ });
743
+ }
744
+
745
+ const getPlaintext = (result) => new TextDecoder().decode(result.plaintext);
746
+ let plaintext;
747
+ if (expectedAlg.match(/^(?:RSA|ECDH)/)) {
748
+ const keystore = await keystores.get(this);
749
+
750
+ const protectedHeader = jose.decodeProtectedHeader(jwe);
751
+
752
+ for (const key of keystore.all({
753
+ ...protectedHeader,
754
+ use: 'enc',
755
+ })) {
756
+ plaintext = await jose
757
+ .compactDecrypt(jwe, await key.keyObject(protectedHeader.alg))
758
+ .then(getPlaintext, () => {});
759
+ if (plaintext) break;
760
+ }
761
+ } else {
762
+ plaintext = await jose
763
+ .compactDecrypt(jwe, this.secretForAlg(expectedAlg === 'dir' ? expectedEnc : expectedAlg))
764
+ .then(getPlaintext, () => {});
765
+ }
766
+
767
+ if (!plaintext) {
768
+ throw new RPError({
769
+ message: 'failed to decrypt JWE',
770
+ jwt: jwe,
771
+ });
772
+ }
773
+ return plaintext;
774
+ }
775
+
776
+ async validateIdToken(tokenSet, nonce, returnedBy, maxAge, state) {
777
+ let idToken = tokenSet;
778
+
779
+ const expectedAlg = this.id_token_signed_response_alg;
780
+
781
+ const isTokenSet = idToken instanceof TokenSet;
782
+
783
+ if (isTokenSet) {
784
+ if (!idToken.id_token) {
785
+ throw new TypeError('id_token not present in TokenSet');
786
+ }
787
+ idToken = idToken.id_token;
788
+ }
789
+
790
+ idToken = String(idToken);
791
+
792
+ const timestamp = now();
793
+ const { protected: header, payload, key } = await this.validateJWT(idToken, expectedAlg);
794
+
795
+ if (typeof maxAge === 'number' || (maxAge !== skipMaxAgeCheck && this.require_auth_time)) {
796
+ if (!payload.auth_time) {
797
+ throw new RPError({
798
+ message: 'missing required JWT property auth_time',
799
+ jwt: idToken,
800
+ });
801
+ }
802
+ if (typeof payload.auth_time !== 'number') {
803
+ throw new RPError({
804
+ message: 'JWT auth_time claim must be a JSON numeric value',
805
+ jwt: idToken,
806
+ });
807
+ }
808
+ }
809
+
810
+ if (
811
+ typeof maxAge === 'number' &&
812
+ payload.auth_time + maxAge < timestamp - this[CLOCK_TOLERANCE]
813
+ ) {
814
+ throw new RPError({
815
+ printf: [
816
+ 'too much time has elapsed since the last End-User authentication, max_age %i, auth_time: %i, now %i',
817
+ maxAge,
818
+ payload.auth_time,
819
+ timestamp - this[CLOCK_TOLERANCE],
820
+ ],
821
+ now: timestamp,
822
+ tolerance: this[CLOCK_TOLERANCE],
823
+ auth_time: payload.auth_time,
824
+ jwt: idToken,
825
+ });
826
+ }
827
+
828
+ if (
829
+ nonce !== skipNonceCheck &&
830
+ (payload.nonce || nonce !== undefined) &&
831
+ payload.nonce !== nonce
832
+ ) {
833
+ throw new RPError({
834
+ printf: ['nonce mismatch, expected %s, got: %s', nonce, payload.nonce],
835
+ jwt: idToken,
836
+ });
837
+ }
838
+
839
+ if (returnedBy === 'authorization') {
840
+ if (!payload.at_hash && tokenSet.access_token) {
841
+ throw new RPError({
842
+ message: 'missing required property at_hash',
843
+ jwt: idToken,
844
+ });
845
+ }
846
+
847
+ if (!payload.c_hash && tokenSet.code) {
848
+ throw new RPError({
849
+ message: 'missing required property c_hash',
850
+ jwt: idToken,
851
+ });
852
+ }
853
+
854
+ if (this.fapi1()) {
855
+ if (!payload.s_hash && (tokenSet.state || state)) {
856
+ throw new RPError({
857
+ message: 'missing required property s_hash',
858
+ jwt: idToken,
859
+ });
860
+ }
861
+ }
862
+
863
+ if (payload.s_hash) {
864
+ if (!state) {
865
+ throw new TypeError('cannot verify s_hash, "checks.state" property not provided');
866
+ }
867
+
868
+ try {
869
+ tokenHash.validate(
870
+ { claim: 's_hash', source: 'state' },
871
+ payload.s_hash,
872
+ state,
873
+ header.alg,
874
+ key.jwk && key.jwk.crv,
875
+ );
876
+ } catch (err) {
877
+ throw new RPError({ message: err.message, jwt: idToken });
878
+ }
879
+ }
880
+ }
881
+
882
+ if (this.fapi() && payload.iat < timestamp - 3600) {
883
+ throw new RPError({
884
+ printf: ['JWT issued too far in the past, now %i, iat %i', timestamp, payload.iat],
885
+ now: timestamp,
886
+ tolerance: this[CLOCK_TOLERANCE],
887
+ iat: payload.iat,
888
+ jwt: idToken,
889
+ });
890
+ }
891
+
892
+ if (tokenSet.access_token && payload.at_hash !== undefined) {
893
+ try {
894
+ tokenHash.validate(
895
+ { claim: 'at_hash', source: 'access_token' },
896
+ payload.at_hash,
897
+ tokenSet.access_token,
898
+ header.alg,
899
+ key.jwk && key.jwk.crv,
900
+ );
901
+ } catch (err) {
902
+ throw new RPError({ message: err.message, jwt: idToken });
903
+ }
904
+ }
905
+
906
+ if (tokenSet.code && payload.c_hash !== undefined) {
907
+ try {
908
+ tokenHash.validate(
909
+ { claim: 'c_hash', source: 'code' },
910
+ payload.c_hash,
911
+ tokenSet.code,
912
+ header.alg,
913
+ key.jwk && key.jwk.crv,
914
+ );
915
+ } catch (err) {
916
+ throw new RPError({ message: err.message, jwt: idToken });
917
+ }
918
+ }
919
+
920
+ return tokenSet;
921
+ }
922
+
923
+ async validateJWT(jwt, expectedAlg, required = ['iss', 'sub', 'aud', 'exp', 'iat']) {
924
+ const isSelfIssued = this.issuer.issuer === 'https://self-issued.me';
925
+ const timestamp = now();
926
+ let header;
927
+ let payload;
928
+ try {
929
+ ({ header, payload } = decodeJWT(jwt, { complete: true }));
930
+ } catch (err) {
931
+ throw new RPError({
932
+ printf: ['failed to decode JWT (%s: %s)', err.name, err.message],
933
+ jwt,
934
+ });
935
+ }
936
+
937
+ if (header.alg !== expectedAlg) {
938
+ throw new RPError({
939
+ printf: ['unexpected JWT alg received, expected %s, got: %s', expectedAlg, header.alg],
940
+ jwt,
941
+ });
942
+ }
943
+
944
+ if (isSelfIssued) {
945
+ required = [...required, 'sub_jwk'];
946
+ }
947
+
948
+ required.forEach(verifyPresence.bind(undefined, payload, jwt));
949
+
950
+ if (payload.iss !== undefined) {
951
+ let expectedIss = this.issuer.issuer;
952
+
953
+ if (this.#aadIssValidation) {
954
+ expectedIss = this.issuer.issuer.replace('{tenantid}', payload.tid);
955
+ }
956
+
957
+ if (payload.iss !== expectedIss) {
958
+ throw new RPError({
959
+ printf: ['unexpected iss value, expected %s, got: %s', expectedIss, payload.iss],
960
+ jwt,
961
+ });
962
+ }
963
+ }
964
+
965
+ if (payload.iat !== undefined) {
966
+ if (typeof payload.iat !== 'number') {
967
+ throw new RPError({
968
+ message: 'JWT iat claim must be a JSON numeric value',
969
+ jwt,
970
+ });
971
+ }
972
+ }
973
+
974
+ if (payload.nbf !== undefined) {
975
+ if (typeof payload.nbf !== 'number') {
976
+ throw new RPError({
977
+ message: 'JWT nbf claim must be a JSON numeric value',
978
+ jwt,
979
+ });
980
+ }
981
+ if (payload.nbf > timestamp + this[CLOCK_TOLERANCE]) {
982
+ throw new RPError({
983
+ printf: [
984
+ 'JWT not active yet, now %i, nbf %i',
985
+ timestamp + this[CLOCK_TOLERANCE],
986
+ payload.nbf,
987
+ ],
988
+ now: timestamp,
989
+ tolerance: this[CLOCK_TOLERANCE],
990
+ nbf: payload.nbf,
991
+ jwt,
992
+ });
993
+ }
994
+ }
995
+
996
+ if (payload.exp !== undefined) {
997
+ if (typeof payload.exp !== 'number') {
998
+ throw new RPError({
999
+ message: 'JWT exp claim must be a JSON numeric value',
1000
+ jwt,
1001
+ });
1002
+ }
1003
+ if (timestamp - this[CLOCK_TOLERANCE] >= payload.exp) {
1004
+ throw new RPError({
1005
+ printf: ['JWT expired, now %i, exp %i', timestamp - this[CLOCK_TOLERANCE], payload.exp],
1006
+ now: timestamp,
1007
+ tolerance: this[CLOCK_TOLERANCE],
1008
+ exp: payload.exp,
1009
+ jwt,
1010
+ });
1011
+ }
1012
+ }
1013
+
1014
+ if (payload.aud !== undefined) {
1015
+ if (Array.isArray(payload.aud)) {
1016
+ if (payload.aud.length > 1 && !payload.azp) {
1017
+ throw new RPError({
1018
+ message: 'missing required JWT property azp',
1019
+ jwt,
1020
+ });
1021
+ }
1022
+
1023
+ if (!payload.aud.includes(this.client_id)) {
1024
+ throw new RPError({
1025
+ printf: [
1026
+ 'aud is missing the client_id, expected %s to be included in %j',
1027
+ this.client_id,
1028
+ payload.aud,
1029
+ ],
1030
+ jwt,
1031
+ });
1032
+ }
1033
+ } else if (payload.aud !== this.client_id) {
1034
+ throw new RPError({
1035
+ printf: ['aud mismatch, expected %s, got: %s', this.client_id, payload.aud],
1036
+ jwt,
1037
+ });
1038
+ }
1039
+ }
1040
+
1041
+ if (payload.azp !== undefined) {
1042
+ let additionalAuthorizedParties = this.#additionalAuthorizedParties;
1043
+
1044
+ if (typeof additionalAuthorizedParties === 'string') {
1045
+ additionalAuthorizedParties = [this.client_id, additionalAuthorizedParties];
1046
+ } else if (Array.isArray(additionalAuthorizedParties)) {
1047
+ additionalAuthorizedParties = [this.client_id, ...additionalAuthorizedParties];
1048
+ } else {
1049
+ additionalAuthorizedParties = [this.client_id];
1050
+ }
1051
+
1052
+ if (!additionalAuthorizedParties.includes(payload.azp)) {
1053
+ throw new RPError({
1054
+ printf: ['azp mismatch, got: %s', payload.azp],
1055
+ jwt,
1056
+ });
1057
+ }
1058
+ }
1059
+
1060
+ let keys;
1061
+
1062
+ if (isSelfIssued) {
1063
+ try {
1064
+ assert(isPlainObject(payload.sub_jwk));
1065
+ const key = await jose.importJWK(payload.sub_jwk, header.alg);
1066
+ assert.equal(key.type, 'public');
1067
+ keys = [
1068
+ {
1069
+ keyObject() {
1070
+ return key;
1071
+ },
1072
+ },
1073
+ ];
1074
+ } catch (err) {
1075
+ throw new RPError({
1076
+ message: 'failed to use sub_jwk claim as an asymmetric JSON Web Key',
1077
+ jwt,
1078
+ });
1079
+ }
1080
+ if ((await jose.calculateJwkThumbprint(payload.sub_jwk)) !== payload.sub) {
1081
+ throw new RPError({
1082
+ message: 'failed to match the subject with sub_jwk',
1083
+ jwt,
1084
+ });
1085
+ }
1086
+ } else if (header.alg.startsWith('HS')) {
1087
+ keys = [this.secretForAlg(header.alg)];
1088
+ } else if (header.alg !== 'none') {
1089
+ keys = await queryKeyStore.call(this.issuer, { ...header, use: 'sig' });
1090
+ }
1091
+
1092
+ if (!keys && header.alg === 'none') {
1093
+ return { protected: header, payload };
1094
+ }
1095
+
1096
+ for (const key of keys) {
1097
+ const verified = await jose
1098
+ .compactVerify(jwt, key instanceof Uint8Array ? key : await key.keyObject(header.alg))
1099
+ .catch(() => {});
1100
+ if (verified) {
1101
+ return {
1102
+ payload,
1103
+ protected: verified.protectedHeader,
1104
+ key,
1105
+ };
1106
+ }
1107
+ }
1108
+
1109
+ throw new RPError({
1110
+ message: 'failed to validate JWT signature',
1111
+ jwt,
1112
+ });
1113
+ }
1114
+
1115
+ async refresh(refreshToken, { exchangeBody, clientAssertionPayload, DPoP } = {}) {
1116
+ let token = refreshToken;
1117
+
1118
+ if (token instanceof TokenSet) {
1119
+ if (!token.refresh_token) {
1120
+ throw new TypeError('refresh_token not present in TokenSet');
1121
+ }
1122
+ token = token.refresh_token;
1123
+ }
1124
+
1125
+ const tokenset = await this.grant(
1126
+ {
1127
+ ...exchangeBody,
1128
+ grant_type: 'refresh_token',
1129
+ refresh_token: String(token),
1130
+ },
1131
+ { clientAssertionPayload, DPoP },
1132
+ );
1133
+
1134
+ if (tokenset.id_token) {
1135
+ await this.decryptIdToken(tokenset);
1136
+ await this.validateIdToken(tokenset, skipNonceCheck, 'token', skipMaxAgeCheck);
1137
+
1138
+ if (refreshToken instanceof TokenSet && refreshToken.id_token) {
1139
+ const expectedSub = refreshToken.claims().sub;
1140
+ const actualSub = tokenset.claims().sub;
1141
+ if (actualSub !== expectedSub) {
1142
+ throw new RPError({
1143
+ printf: ['sub mismatch, expected %s, got: %s', expectedSub, actualSub],
1144
+ jwt: tokenset.id_token,
1145
+ });
1146
+ }
1147
+ }
1148
+ }
1149
+
1150
+ return tokenset;
1151
+ }
1152
+
1153
+ async requestResource(
1154
+ resourceUrl,
1155
+ accessToken,
1156
+ {
1157
+ method,
1158
+ headers,
1159
+ body,
1160
+ DPoP,
1161
+ tokenType = DPoP
1162
+ ? 'DPoP'
1163
+ : accessToken instanceof TokenSet
1164
+ ? accessToken.token_type
1165
+ : 'Bearer',
1166
+ } = {},
1167
+ retry,
1168
+ ) {
1169
+ if (accessToken instanceof TokenSet) {
1170
+ if (!accessToken.access_token) {
1171
+ throw new TypeError('access_token not present in TokenSet');
1172
+ }
1173
+ accessToken = accessToken.access_token;
1174
+ }
1175
+
1176
+ if (!accessToken) {
1177
+ throw new TypeError('no access token provided');
1178
+ } else if (typeof accessToken !== 'string') {
1179
+ throw new TypeError('invalid access token provided');
1180
+ }
1181
+
1182
+ const requestOpts = {
1183
+ headers: {
1184
+ Authorization: authorizationHeaderValue(accessToken, tokenType),
1185
+ ...headers,
1186
+ },
1187
+ body,
1188
+ };
1189
+
1190
+ const mTLS = !!this.tls_client_certificate_bound_access_tokens;
1191
+
1192
+ const response = await request.call(
1193
+ this,
1194
+ {
1195
+ ...requestOpts,
1196
+ responseType: 'buffer',
1197
+ method,
1198
+ url: resourceUrl,
1199
+ },
1200
+ { accessToken, mTLS, DPoP },
1201
+ );
1202
+
1203
+ const wwwAuthenticate = response.headers['www-authenticate'];
1204
+ if (
1205
+ retry !== retryAttempt &&
1206
+ wwwAuthenticate &&
1207
+ wwwAuthenticate.toLowerCase().startsWith('dpop ') &&
1208
+ parseWwwAuthenticate(wwwAuthenticate).error === 'use_dpop_nonce'
1209
+ ) {
1210
+ return this.requestResource(resourceUrl, accessToken, {
1211
+ method,
1212
+ headers,
1213
+ body,
1214
+ DPoP,
1215
+ tokenType,
1216
+ });
1217
+ }
1218
+
1219
+ return response;
1220
+ }
1221
+
1222
+ async userinfo(accessToken, { method = 'GET', via = 'header', tokenType, params, DPoP } = {}) {
1223
+ assertIssuerConfiguration(this.issuer, 'userinfo_endpoint');
1224
+ const options = {
1225
+ tokenType,
1226
+ method: String(method).toUpperCase(),
1227
+ DPoP,
1228
+ };
1229
+
1230
+ if (options.method !== 'GET' && options.method !== 'POST') {
1231
+ throw new TypeError('#userinfo() method can only be POST or a GET');
1232
+ }
1233
+
1234
+ if (via === 'body' && options.method !== 'POST') {
1235
+ throw new TypeError('can only send body on POST');
1236
+ }
1237
+
1238
+ const jwt = !!(this.userinfo_signed_response_alg || this.userinfo_encrypted_response_alg);
1239
+
1240
+ if (jwt) {
1241
+ options.headers = { Accept: 'application/jwt' };
1242
+ } else {
1243
+ options.headers = { Accept: 'application/json' };
1244
+ }
1245
+ const mTLS = !!this.tls_client_certificate_bound_access_tokens;
1246
+
1247
+ let targetUrl;
1248
+ if (mTLS && this.issuer.mtls_endpoint_aliases) {
1249
+ targetUrl = this.issuer.mtls_endpoint_aliases.userinfo_endpoint;
1250
+ }
1251
+
1252
+ targetUrl = new URL(targetUrl || this.issuer.userinfo_endpoint);
1253
+
1254
+ if (via === 'body') {
1255
+ options.headers.Authorization = undefined;
1256
+ options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
1257
+ options.body = new URLSearchParams();
1258
+ options.body.append(
1259
+ 'access_token',
1260
+ accessToken instanceof TokenSet ? accessToken.access_token : accessToken,
1261
+ );
1262
+ }
1263
+
1264
+ // handle additional parameters, GET via querystring, POST via urlencoded body
1265
+ if (params) {
1266
+ if (options.method === 'GET') {
1267
+ Object.entries(params).forEach(([key, value]) => {
1268
+ targetUrl.searchParams.append(key, value);
1269
+ });
1270
+ } else if (options.body) {
1271
+ // POST && via body
1272
+ Object.entries(params).forEach(([key, value]) => {
1273
+ options.body.append(key, value);
1274
+ });
1275
+ } else {
1276
+ // POST && via header
1277
+ options.body = new URLSearchParams();
1278
+ options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
1279
+ Object.entries(params).forEach(([key, value]) => {
1280
+ options.body.append(key, value);
1281
+ });
1282
+ }
1283
+ }
1284
+
1285
+ if (options.body) {
1286
+ options.body = options.body.toString();
1287
+ }
1288
+
1289
+ const response = await this.requestResource(targetUrl, accessToken, options);
1290
+
1291
+ let parsed = processResponse(response, { bearer: true });
1292
+
1293
+ if (jwt) {
1294
+ if (!/^application\/jwt/.test(response.headers['content-type'])) {
1295
+ throw new RPError({
1296
+ message: 'expected application/jwt response from the userinfo_endpoint',
1297
+ response,
1298
+ });
1299
+ }
1300
+
1301
+ const body = response.body.toString();
1302
+ const userinfo = await this.decryptJWTUserinfo(body);
1303
+ if (!this.userinfo_signed_response_alg) {
1304
+ try {
1305
+ parsed = JSON.parse(userinfo);
1306
+ assert(isPlainObject(parsed));
1307
+ } catch (err) {
1308
+ throw new RPError({
1309
+ message: 'failed to parse userinfo JWE payload as JSON',
1310
+ jwt: userinfo,
1311
+ });
1312
+ }
1313
+ } else {
1314
+ ({ payload: parsed } = await this.validateJWTUserinfo(userinfo));
1315
+ }
1316
+ } else {
1317
+ try {
1318
+ parsed = JSON.parse(response.body);
1319
+ } catch (err) {
1320
+ Object.defineProperty(err, 'response', { value: response });
1321
+ throw err;
1322
+ }
1323
+ }
1324
+
1325
+ if (accessToken instanceof TokenSet && accessToken.id_token) {
1326
+ const expectedSub = accessToken.claims().sub;
1327
+ if (parsed.sub !== expectedSub) {
1328
+ throw new RPError({
1329
+ printf: ['userinfo sub mismatch, expected %s, got: %s', expectedSub, parsed.sub],
1330
+ body: parsed,
1331
+ jwt: accessToken.id_token,
1332
+ });
1333
+ }
1334
+ }
1335
+
1336
+ return parsed;
1337
+ }
1338
+
1339
+ encryptionSecret(len) {
1340
+ const hash = len <= 256 ? 'sha256' : len <= 384 ? 'sha384' : len <= 512 ? 'sha512' : false;
1341
+ if (!hash) {
1342
+ throw new Error('unsupported symmetric encryption key derivation');
1343
+ }
1344
+
1345
+ return crypto
1346
+ .createHash(hash)
1347
+ .update(this.client_secret)
1348
+ .digest()
1349
+ .slice(0, len / 8);
1350
+ }
1351
+
1352
+ secretForAlg(alg) {
1353
+ if (!this.client_secret) {
1354
+ throw new TypeError('client_secret is required');
1355
+ }
1356
+
1357
+ if (/^A(\d{3})(?:GCM)?KW$/.test(alg)) {
1358
+ return this.encryptionSecret(parseInt(RegExp.$1, 10));
1359
+ }
1360
+
1361
+ if (/^A(\d{3})(?:GCM|CBC-HS(\d{3}))$/.test(alg)) {
1362
+ return this.encryptionSecret(parseInt(RegExp.$2 || RegExp.$1, 10));
1363
+ }
1364
+
1365
+ return new TextEncoder().encode(this.client_secret);
1366
+ }
1367
+
1368
+ async grant(body, { clientAssertionPayload, DPoP } = {}, retry) {
1369
+ assertIssuerConfiguration(this.issuer, 'token_endpoint');
1370
+ const response = await authenticatedPost.call(
1371
+ this,
1372
+ 'token',
1373
+ {
1374
+ form: body,
1375
+ responseType: 'json',
1376
+ },
1377
+ { clientAssertionPayload, DPoP },
1378
+ );
1379
+ let responseBody;
1380
+ try {
1381
+ responseBody = processResponse(response);
1382
+ } catch (err) {
1383
+ if (retry !== retryAttempt && err instanceof OPError && err.error === 'use_dpop_nonce') {
1384
+ return this.grant(body, { clientAssertionPayload, DPoP }, retryAttempt);
1385
+ }
1386
+ throw err;
1387
+ }
1388
+
1389
+ return new TokenSet(responseBody);
1390
+ }
1391
+
1392
+ async deviceAuthorization(params = {}, { exchangeBody, clientAssertionPayload, DPoP } = {}) {
1393
+ assertIssuerConfiguration(this.issuer, 'device_authorization_endpoint');
1394
+ assertIssuerConfiguration(this.issuer, 'token_endpoint');
1395
+
1396
+ const body = authorizationParams.call(this, {
1397
+ client_id: this.client_id,
1398
+ redirect_uri: null,
1399
+ response_type: null,
1400
+ ...params,
1401
+ });
1402
+
1403
+ const response = await authenticatedPost.call(
1404
+ this,
1405
+ 'device_authorization',
1406
+ {
1407
+ responseType: 'json',
1408
+ form: body,
1409
+ },
1410
+ { clientAssertionPayload, endpointAuthMethod: 'token' },
1411
+ );
1412
+ const responseBody = processResponse(response);
1413
+
1414
+ return new DeviceFlowHandle({
1415
+ client: this,
1416
+ exchangeBody,
1417
+ clientAssertionPayload,
1418
+ response: responseBody,
1419
+ maxAge: params.max_age,
1420
+ DPoP,
1421
+ });
1422
+ }
1423
+
1424
+ async revoke(token, hint, { revokeBody, clientAssertionPayload } = {}) {
1425
+ assertIssuerConfiguration(this.issuer, 'revocation_endpoint');
1426
+ if (hint !== undefined && typeof hint !== 'string') {
1427
+ throw new TypeError('hint must be a string');
1428
+ }
1429
+
1430
+ const form = { ...revokeBody, token };
1431
+
1432
+ if (hint) {
1433
+ form.token_type_hint = hint;
1434
+ }
1435
+
1436
+ const response = await authenticatedPost.call(
1437
+ this,
1438
+ 'revocation',
1439
+ {
1440
+ form,
1441
+ },
1442
+ { clientAssertionPayload },
1443
+ );
1444
+ processResponse(response, { body: false });
1445
+ }
1446
+
1447
+ async introspect(token, hint, { introspectBody, clientAssertionPayload } = {}) {
1448
+ assertIssuerConfiguration(this.issuer, 'introspection_endpoint');
1449
+ if (hint !== undefined && typeof hint !== 'string') {
1450
+ throw new TypeError('hint must be a string');
1451
+ }
1452
+
1453
+ const form = { ...introspectBody, token };
1454
+ if (hint) {
1455
+ form.token_type_hint = hint;
1456
+ }
1457
+
1458
+ const response = await authenticatedPost.call(
1459
+ this,
1460
+ 'introspection',
1461
+ { form, responseType: 'json' },
1462
+ { clientAssertionPayload },
1463
+ );
1464
+
1465
+ const responseBody = processResponse(response);
1466
+
1467
+ return responseBody;
1468
+ }
1469
+
1470
+ static async register(metadata, options = {}) {
1471
+ const { initialAccessToken, jwks, ...clientOptions } = options;
1472
+
1473
+ assertIssuerConfiguration(this.issuer, 'registration_endpoint');
1474
+
1475
+ if (jwks !== undefined && !(metadata.jwks || metadata.jwks_uri)) {
1476
+ const keystore = await getKeystore.call(this, jwks);
1477
+ metadata.jwks = keystore.toJWKS();
1478
+ }
1479
+
1480
+ const response = await request.call(this, {
1481
+ headers: {
1482
+ Accept: 'application/json',
1483
+ ...(initialAccessToken
1484
+ ? {
1485
+ Authorization: authorizationHeaderValue(initialAccessToken),
1486
+ }
1487
+ : undefined),
1488
+ },
1489
+ responseType: 'json',
1490
+ json: metadata,
1491
+ url: this.issuer.registration_endpoint,
1492
+ method: 'POST',
1493
+ });
1494
+ const responseBody = processResponse(response, { statusCode: 201, bearer: true });
1495
+
1496
+ return new this(responseBody, jwks, clientOptions);
1497
+ }
1498
+
1499
+ get metadata() {
1500
+ return clone(Object.fromEntries(this.#metadata.entries()));
1501
+ }
1502
+
1503
+ static async fromUri(registrationClientUri, registrationAccessToken, jwks, clientOptions) {
1504
+ const response = await request.call(this, {
1505
+ method: 'GET',
1506
+ url: registrationClientUri,
1507
+ responseType: 'json',
1508
+ headers: {
1509
+ Authorization: authorizationHeaderValue(registrationAccessToken),
1510
+ Accept: 'application/json',
1511
+ },
1512
+ });
1513
+ const responseBody = processResponse(response, { bearer: true });
1514
+
1515
+ return new this(responseBody, jwks, clientOptions);
1516
+ }
1517
+
1518
+ async requestObject(
1519
+ requestObject = {},
1520
+ {
1521
+ sign: signingAlgorithm = this.request_object_signing_alg || 'none',
1522
+ encrypt: {
1523
+ alg: eKeyManagement = this.request_object_encryption_alg,
1524
+ enc: eContentEncryption = this.request_object_encryption_enc || 'A128CBC-HS256',
1525
+ } = {},
1526
+ } = {},
1527
+ ) {
1528
+ if (!isPlainObject(requestObject)) {
1529
+ throw new TypeError('requestObject must be a plain object');
1530
+ }
1531
+
1532
+ let signed;
1533
+ let key;
1534
+ const unix = now();
1535
+ const header = { alg: signingAlgorithm, typ: 'oauth-authz-req+jwt' };
1536
+ const payload = JSON.stringify(
1537
+ defaults({}, requestObject, {
1538
+ iss: this.client_id,
1539
+ aud: this.issuer.issuer,
1540
+ client_id: this.client_id,
1541
+ jti: random(),
1542
+ iat: unix,
1543
+ exp: unix + 300,
1544
+ ...(this.fapi() ? { nbf: unix } : undefined),
1545
+ }),
1546
+ );
1547
+ if (signingAlgorithm === 'none') {
1548
+ signed = [base64url.encode(JSON.stringify(header)), base64url.encode(payload), ''].join('.');
1549
+ } else {
1550
+ const symmetric = signingAlgorithm.startsWith('HS');
1551
+ if (symmetric) {
1552
+ key = this.secretForAlg(signingAlgorithm);
1553
+ } else {
1554
+ const keystore = await keystores.get(this);
1555
+
1556
+ if (!keystore) {
1557
+ throw new TypeError(
1558
+ `no keystore present for client, cannot sign using alg ${signingAlgorithm}`,
1559
+ );
1560
+ }
1561
+ key = keystore.get({ alg: signingAlgorithm, use: 'sig' });
1562
+ if (!key) {
1563
+ throw new TypeError(`no key to sign with found for alg ${signingAlgorithm}`);
1564
+ }
1565
+ }
1566
+
1567
+ signed = await new jose.CompactSign(new TextEncoder().encode(payload))
1568
+ .setProtectedHeader({
1569
+ ...header,
1570
+ kid: symmetric ? undefined : key.jwk.kid,
1571
+ })
1572
+ .sign(symmetric ? key : await key.keyObject(signingAlgorithm));
1573
+ }
1574
+
1575
+ if (!eKeyManagement) {
1576
+ return signed;
1577
+ }
1578
+
1579
+ const fields = { alg: eKeyManagement, enc: eContentEncryption, cty: 'oauth-authz-req+jwt' };
1580
+
1581
+ if (fields.alg.match(/^(RSA|ECDH)/)) {
1582
+ [key] = await queryKeyStore.call(
1583
+ this.issuer,
1584
+ { alg: fields.alg, use: 'enc' },
1585
+ { allowMulti: true },
1586
+ );
1587
+ } else {
1588
+ key = this.secretForAlg(fields.alg === 'dir' ? fields.enc : fields.alg);
1589
+ }
1590
+
1591
+ return new jose.CompactEncrypt(new TextEncoder().encode(signed))
1592
+ .setProtectedHeader({
1593
+ ...fields,
1594
+ kid: key instanceof Uint8Array ? undefined : key.jwk.kid,
1595
+ })
1596
+ .encrypt(key instanceof Uint8Array ? key : await key.keyObject(fields.alg));
1597
+ }
1598
+
1599
+ async pushedAuthorizationRequest(params = {}, { clientAssertionPayload } = {}) {
1600
+ assertIssuerConfiguration(this.issuer, 'pushed_authorization_request_endpoint');
1601
+
1602
+ const body = {
1603
+ ...('request' in params ? params : authorizationParams.call(this, params)),
1604
+ client_id: this.client_id,
1605
+ };
1606
+
1607
+ const response = await authenticatedPost.call(
1608
+ this,
1609
+ 'pushed_authorization_request',
1610
+ {
1611
+ responseType: 'json',
1612
+ form: body,
1613
+ },
1614
+ { clientAssertionPayload, endpointAuthMethod: 'token' },
1615
+ );
1616
+ const responseBody = processResponse(response, { statusCode: 201 });
1617
+
1618
+ if (!('expires_in' in responseBody)) {
1619
+ throw new RPError({
1620
+ message: 'expected expires_in in Pushed Authorization Successful Response',
1621
+ response,
1622
+ });
1623
+ }
1624
+ if (typeof responseBody.expires_in !== 'number') {
1625
+ throw new RPError({
1626
+ message: 'invalid expires_in value in Pushed Authorization Successful Response',
1627
+ response,
1628
+ });
1629
+ }
1630
+ if (!('request_uri' in responseBody)) {
1631
+ throw new RPError({
1632
+ message: 'expected request_uri in Pushed Authorization Successful Response',
1633
+ response,
1634
+ });
1635
+ }
1636
+ if (typeof responseBody.request_uri !== 'string') {
1637
+ throw new RPError({
1638
+ message: 'invalid request_uri value in Pushed Authorization Successful Response',
1639
+ response,
1640
+ });
1641
+ }
1642
+
1643
+ return responseBody;
1644
+ }
1645
+
1646
+ get issuer() {
1647
+ return this.#issuer;
1648
+ }
1649
+
1650
+ /* istanbul ignore next */
1651
+ [inspect.custom]() {
1652
+ return `${this.constructor.name} ${inspect(this.metadata, {
1653
+ depth: Infinity,
1654
+ colors: process.stdout.isTTY,
1655
+ compact: false,
1656
+ sorted: true,
1657
+ })}`;
1658
+ }
1659
+
1660
+ fapi() {
1661
+ return this.fapi1() || this.fapi2();
1662
+ }
1663
+
1664
+ fapi1() {
1665
+ return this.constructor.name === 'FAPI1Client';
1666
+ }
1667
+
1668
+ fapi2() {
1669
+ return this.constructor.name === 'FAPI2Client';
1670
+ }
1671
+
1672
+ async validateJARM(response) {
1673
+ const expectedAlg = this.authorization_signed_response_alg;
1674
+ const { payload } = await this.validateJWT(response, expectedAlg, ['iss', 'exp', 'aud']);
1675
+ return pickCb(payload);
1676
+ }
1677
+
1678
+ /**
1679
+ * @name dpopProof
1680
+ * @api private
1681
+ */
1682
+ async dpopProof(payload, privateKeyInput, accessToken) {
1683
+ if (!isPlainObject(payload)) {
1684
+ throw new TypeError('payload must be a plain object');
1685
+ }
1686
+
1687
+ let privateKey;
1688
+ if (isKeyObject(privateKeyInput)) {
1689
+ privateKey = privateKeyInput;
1690
+ } else if (privateKeyInput[Symbol.toStringTag] === 'CryptoKey') {
1691
+ privateKey = privateKeyInput;
1692
+ } else if (jose.cryptoRuntime === 'node:crypto') {
1693
+ privateKey = crypto.createPrivateKey(privateKeyInput);
1694
+ } else {
1695
+ throw new TypeError('unrecognized crypto runtime');
1696
+ }
1697
+
1698
+ if (privateKey.type !== 'private') {
1699
+ throw new TypeError('"DPoP" option must be a private key');
1700
+ }
1701
+ let alg = determineDPoPAlgorithm.call(this, privateKey, privateKeyInput);
1702
+
1703
+ if (!alg) {
1704
+ throw new TypeError('could not determine DPoP JWS Algorithm');
1705
+ }
1706
+
1707
+ return new jose.SignJWT({
1708
+ ath: accessToken
1709
+ ? base64url.encode(crypto.createHash('sha256').update(accessToken).digest())
1710
+ : undefined,
1711
+ ...payload,
1712
+ })
1713
+ .setProtectedHeader({
1714
+ alg,
1715
+ typ: 'dpop+jwt',
1716
+ jwk: await getJwk(privateKey, privateKeyInput),
1717
+ })
1718
+ .setIssuedAt()
1719
+ .setJti(random())
1720
+ .sign(privateKey);
1721
+ }
1722
+ }
1723
+
1724
+ function determineDPoPAlgorithmFromCryptoKey(cryptoKey) {
1725
+ switch (cryptoKey.algorithm.name) {
1726
+ case 'Ed25519':
1727
+ case 'Ed448':
1728
+ return 'EdDSA';
1729
+ case 'ECDSA': {
1730
+ switch (cryptoKey.algorithm.namedCurve) {
1731
+ case 'P-256':
1732
+ return 'ES256';
1733
+ case 'P-384':
1734
+ return 'ES384';
1735
+ case 'P-521':
1736
+ return 'ES512';
1737
+ default:
1738
+ break;
1739
+ }
1740
+ break;
1741
+ }
1742
+ case 'RSASSA-PKCS1-v1_5':
1743
+ return `RS${cryptoKey.algorithm.hash.name.slice(4)}`;
1744
+ case 'RSA-PSS':
1745
+ return `PS${cryptoKey.algorithm.hash.name.slice(4)}`;
1746
+ default:
1747
+ throw new TypeError('unsupported DPoP private key');
1748
+ }
1749
+ }
1750
+
1751
+ let determineDPoPAlgorithm;
1752
+ if (jose.cryptoRuntime === 'node:crypto') {
1753
+ determineDPoPAlgorithm = function (privateKey, privateKeyInput) {
1754
+ if (privateKeyInput[Symbol.toStringTag] === 'CryptoKey') {
1755
+ return determineDPoPAlgorithmFromCryptoKey(privateKey);
1756
+ }
1757
+
1758
+ switch (privateKey.asymmetricKeyType) {
1759
+ case 'ed25519':
1760
+ case 'ed448':
1761
+ return 'EdDSA';
1762
+ case 'ec':
1763
+ return determineEcAlgorithm(privateKey, privateKeyInput);
1764
+ case 'rsa':
1765
+ case rsaPssParams && 'rsa-pss':
1766
+ return determineRsaAlgorithm(
1767
+ privateKey,
1768
+ privateKeyInput,
1769
+ this.issuer.dpop_signing_alg_values_supported,
1770
+ );
1771
+ default:
1772
+ throw new TypeError('unsupported DPoP private key');
1773
+ }
1774
+ };
1775
+
1776
+ const RSPS = /^(?:RS|PS)(?:256|384|512)$/;
1777
+ function determineRsaAlgorithm(privateKey, privateKeyInput, valuesSupported) {
1778
+ if (
1779
+ typeof privateKeyInput === 'object' &&
1780
+ privateKeyInput.format === 'jwk' &&
1781
+ privateKeyInput.key &&
1782
+ privateKeyInput.key.alg
1783
+ ) {
1784
+ return privateKeyInput.key.alg;
1785
+ }
1786
+
1787
+ if (Array.isArray(valuesSupported)) {
1788
+ let candidates = valuesSupported.filter(RegExp.prototype.test.bind(RSPS));
1789
+ if (privateKey.asymmetricKeyType === 'rsa-pss') {
1790
+ candidates = candidates.filter((value) => value.startsWith('PS'));
1791
+ }
1792
+ return ['PS256', 'PS384', 'PS512', 'RS256', 'RS384', 'RS384'].find((preferred) =>
1793
+ candidates.includes(preferred),
1794
+ );
1795
+ }
1796
+
1797
+ return 'PS256';
1798
+ }
1799
+
1800
+ const p256 = Buffer.from([42, 134, 72, 206, 61, 3, 1, 7]);
1801
+ const p384 = Buffer.from([43, 129, 4, 0, 34]);
1802
+ const p521 = Buffer.from([43, 129, 4, 0, 35]);
1803
+ const secp256k1 = Buffer.from([43, 129, 4, 0, 10]);
1804
+
1805
+ function determineEcAlgorithm(privateKey, privateKeyInput) {
1806
+ // If input was a JWK
1807
+ switch (
1808
+ typeof privateKeyInput === 'object' &&
1809
+ typeof privateKeyInput.key === 'object' &&
1810
+ privateKeyInput.key.crv
1811
+ ) {
1812
+ case 'P-256':
1813
+ return 'ES256';
1814
+ case 'secp256k1':
1815
+ return 'ES256K';
1816
+ case 'P-384':
1817
+ return 'ES384';
1818
+ case 'P-512':
1819
+ return 'ES512';
1820
+ default:
1821
+ break;
1822
+ }
1823
+
1824
+ const buf = privateKey.export({ format: 'der', type: 'pkcs8' });
1825
+ const i = buf[1] < 128 ? 17 : 18;
1826
+ const len = buf[i];
1827
+ const curveOid = buf.slice(i + 1, i + 1 + len);
1828
+ if (curveOid.equals(p256)) {
1829
+ return 'ES256';
1830
+ }
1831
+
1832
+ if (curveOid.equals(p384)) {
1833
+ return 'ES384';
1834
+ }
1835
+ if (curveOid.equals(p521)) {
1836
+ return 'ES512';
1837
+ }
1838
+
1839
+ if (curveOid.equals(secp256k1)) {
1840
+ return 'ES256K';
1841
+ }
1842
+
1843
+ throw new TypeError('unsupported DPoP private key curve');
1844
+ }
1845
+ } else {
1846
+ determineDPoPAlgorithm = determineDPoPAlgorithmFromCryptoKey;
1847
+ }
1848
+
1849
+ const jwkCache = new WeakMap();
1850
+ async function getJwk(keyObject, privateKeyInput) {
1851
+ if (
1852
+ jose.cryptoRuntime === 'node:crypto' &&
1853
+ typeof privateKeyInput === 'object' &&
1854
+ typeof privateKeyInput.key === 'object' &&
1855
+ privateKeyInput.format === 'jwk'
1856
+ ) {
1857
+ return pick(privateKeyInput.key, 'kty', 'crv', 'x', 'y', 'e', 'n');
1858
+ }
1859
+
1860
+ if (jwkCache.has(privateKeyInput)) {
1861
+ return jwkCache.get(privateKeyInput);
1862
+ }
1863
+
1864
+ const jwk = pick(await jose.exportJWK(keyObject), 'kty', 'crv', 'x', 'y', 'e', 'n');
1865
+
1866
+ if (isKeyObject(privateKeyInput) || jose.cryptoRuntime === 'WebCryptoAPI') {
1867
+ jwkCache.set(privateKeyInput, jwk);
1868
+ }
1869
+
1870
+ return jwk;
1871
+ }
1872
+
1873
+ module.exports = (issuer, aadIssValidation = false) =>
1874
+ class Client extends BaseClient {
1875
+ constructor(...args) {
1876
+ super(issuer, aadIssValidation, ...args);
1877
+ }
1878
+
1879
+ static get issuer() {
1880
+ return issuer;
1881
+ }
1882
+ };
1883
+
1884
+ module.exports.BaseClient = BaseClient;