@mongodb-js/oidc-plugin 1.1.8 → 2.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/dist/plugin.js CHANGED
@@ -15,25 +15,35 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.MongoDBOIDCPluginImpl = exports.automaticRefreshTimeoutMS = void 0;
36
+ exports.MongoDBOIDCPluginImpl = void 0;
37
+ exports.automaticRefreshTimeoutMS = automaticRefreshTimeoutMS;
27
38
  const types_1 = require("./types");
28
39
  const util_1 = require("./util");
29
- const openid_client_1 = require("openid-client");
30
- const openid_client_2 = require("openid-client");
31
40
  const rfc_8252_http_server_1 = require("./rfc-8252-http-server");
32
- const util_2 = require("util");
33
- const crypto_1 = require("crypto");
34
41
  const events_1 = require("events");
35
42
  const api_1 = require("./api");
36
43
  const child_process_1 = require("child_process");
44
+ const openid_client_1 = require("openid-client");
45
+ const https_1 = require("https");
46
+ const http_1 = require("http");
37
47
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
38
48
  let _electron = undefined;
39
49
  async function getDefaultOpenBrowser() {
@@ -41,7 +51,7 @@ async function getDefaultOpenBrowser() {
41
51
  // to open a browser.
42
52
  if (process.versions.electron && _electron !== 'cannot-require') {
43
53
  try {
44
- _electron ?? (_electron = await Promise.resolve().then(() => __importStar(require('electron'))));
54
+ _electron ??= await Promise.resolve().then(() => __importStar(require('electron')));
45
55
  return ({ url }) =>
46
56
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
47
57
  _electron.shell.openExternal(url);
@@ -88,17 +98,17 @@ function tokenExpiryInSeconds(tokenSet = {}, passIdTokenAsAccessToken = false, n
88
98
  // specifies the token expiry. Otherwise, we assume that the `expires_at`
89
99
  // value presented by the OIDC provider is correct, since OIDC clients are
90
100
  // not supposed to inspect access tokens and treat them as opaque.
91
- const expiresAt = (tokenSet.id_token &&
101
+ const expiresAt = (tokenSet.idTokenClaims?.exp !== undefined &&
92
102
  passIdTokenAsAccessToken &&
93
- tokenSet.claims?.().exp) ||
94
- tokenSet.expires_at ||
103
+ tokenSet.idTokenClaims?.exp) ||
104
+ tokenSet.expiresAt ||
95
105
  0;
96
106
  return Math.max(0, (expiresAt ?? 0) - now / 1000);
97
107
  }
98
108
  /** @internal Exported for testing only */
99
109
  function automaticRefreshTimeoutMS(tokenSet, passIdTokenAsAccessToken = false, now = Date.now()) {
100
110
  const expiresIn = tokenExpiryInSeconds(tokenSet, passIdTokenAsAccessToken, now);
101
- if (!tokenSet.refresh_token || !expiresIn)
111
+ if (!tokenSet.refreshToken || !expiresIn)
102
112
  return;
103
113
  // If the tokens expire in more than 1 minute, automatically register
104
114
  // a refresh handler. (They should not expire in less; however,
@@ -110,7 +120,6 @@ function automaticRefreshTimeoutMS(tokenSet, passIdTokenAsAccessToken = false, n
110
120
  return Math.max(expiresIn - 300 /* 5 minutes */, expiresIn / 2) * 1000;
111
121
  }
112
122
  }
113
- exports.automaticRefreshTimeoutMS = automaticRefreshTimeoutMS;
114
123
  const kEnableFallback = Symbol.for('@@mdb.oidcplugin.kEnableFallback');
115
124
  let updateIdCounter = 0;
116
125
  let authStateIdCounter = 0;
@@ -126,9 +135,13 @@ function allowFallbackIfFailed(promise) {
126
135
  }
127
136
  /** @internal */
128
137
  class MongoDBOIDCPluginImpl {
138
+ options;
139
+ logger;
140
+ mapIdpToAuthState = new Map();
141
+ mongoClientOptions;
142
+ timers;
143
+ destroyed = false;
129
144
  constructor(options) {
130
- this.mapIdpToAuthState = new Map();
131
- this.destroyed = false;
132
145
  this.options = options;
133
146
  this.logger = options.logger ?? new events_1.EventEmitter();
134
147
  this.mongoClientOptions = {
@@ -154,10 +167,10 @@ class MongoDBOIDCPluginImpl {
154
167
  original = JSON.parse(Buffer.from(serialized, 'base64').toString('utf8'));
155
168
  }
156
169
  catch (err) {
157
- throw new types_1.MongoDBOIDCError(`Stored OIDC data could not be deserialized: ${err.message}`);
170
+ throw new types_1.MongoDBOIDCError(`Stored OIDC data could not be deserialized: ${err.message}`, { cause: err, codeName: 'DeserializeFormatMismatch' });
158
171
  }
159
- if (original.oidcPluginStateVersion !== 0) {
160
- throw new types_1.MongoDBOIDCError(`Stored OIDC data could not be deserialized because of a version mismatch (got ${JSON.stringify(original.oidcPluginStateVersion)}, expected 0)`);
172
+ if (original.oidcPluginStateVersion !== 1) {
173
+ throw new types_1.MongoDBOIDCError(`Stored OIDC data could not be deserialized because of a version mismatch (got ${JSON.stringify(original.oidcPluginStateVersion)}, expected 1)`, { codeName: 'DeserializeVersionMismatch' });
161
174
  }
162
175
  for (const [key, serializedState] of original.state) {
163
176
  const state = {
@@ -170,7 +183,9 @@ class MongoDBOIDCPluginImpl {
170
183
  : undefined,
171
184
  discardingTokenSets: serializedState.discardingTokenSets,
172
185
  };
173
- this.updateStateWithTokenSet(state, new openid_client_1.TokenSet(serializedState.currentTokenSet.set));
186
+ if (serializedState.currentTokenSet.set) {
187
+ this.updateStateWithTokenSet(state, util_1.TokenSet.fromSerialized(serializedState.currentTokenSet.set));
188
+ }
174
189
  this.mapIdpToAuthState.set(key, state);
175
190
  }
176
191
  }
@@ -188,7 +203,7 @@ class MongoDBOIDCPluginImpl {
188
203
  // Separate method so we can re-use the inferred return type in _deserialize()
189
204
  _serialize() {
190
205
  return {
191
- oidcPluginStateVersion: 0,
206
+ oidcPluginStateVersion: 1,
192
207
  state: [...this.mapIdpToAuthState]
193
208
  .filter(([, state]) => !!state.currentTokenSet)
194
209
  .map(([key, state]) => {
@@ -198,7 +213,7 @@ class MongoDBOIDCPluginImpl {
198
213
  id: state.id,
199
214
  serverOIDCMetadata: { ...state.serverOIDCMetadata },
200
215
  currentTokenSet: {
201
- set: { ...state.currentTokenSet?.set },
216
+ set: state.currentTokenSet?.set?.serialize(),
202
217
  },
203
218
  lastIdTokenClaims: state.lastIdTokenClaims
204
219
  ? { ...state.lastIdTokenClaims }
@@ -231,11 +246,13 @@ class MongoDBOIDCPluginImpl {
231
246
  // or create a new one if none exists.
232
247
  getAuthState(serverMetadata) {
233
248
  if (!serverMetadata.issuer || typeof serverMetadata.issuer !== 'string') {
234
- throw new types_1.MongoDBOIDCError(`'issuer' is missing`);
249
+ throw new types_1.MongoDBOIDCError(`'issuer' is missing`, {
250
+ codeName: 'MissingIssuer',
251
+ });
235
252
  }
236
253
  (0, util_1.validateSecureHTTPUrl)(serverMetadata.issuer, 'issuer');
237
254
  if (!serverMetadata.clientId) {
238
- throw new types_1.MongoDBOIDCError('No clientId passed in server OIDC metadata object');
255
+ throw new types_1.MongoDBOIDCError('No clientId passed in server OIDC metadata object', { codeName: 'MissingClientId' });
239
256
  }
240
257
  const key = JSON.stringify({
241
258
  // If any part of the server metadata changes, we should probably use
@@ -254,95 +271,102 @@ class MongoDBOIDCPluginImpl {
254
271
  this.mapIdpToAuthState.set(key, newState);
255
272
  return newState;
256
273
  }
257
- getSupportedDefaultScopes(issuer) {
274
+ getSupportedDefaultScopes(idpMetadata) {
258
275
  return ['openid', 'offline_access'].filter((scope) => {
259
276
  // Only add `openid` / `offline_access` if the IdP announces support
260
277
  // for those scopes, or if the IdP does not provide a list of scopes
261
278
  // and we cannot tell which it supports.
262
279
  // https://jira.mongodb.org/browser/COMPASS-7437
263
- return (!Array.isArray(issuer.metadata.scopes_supported) ||
264
- issuer.metadata.scopes_supported.includes(scope));
280
+ return (!Array.isArray(idpMetadata.scopes_supported) ||
281
+ idpMetadata.scopes_supported.includes(scope));
265
282
  });
266
283
  }
267
- // prettier does not understand the return type syntax, TS does
268
- getIssuerClass() {
269
- var _a, _b;
270
- const oidcPluginCustomHttpOptionsProvider = (urlObject, options) => {
271
- const url = urlObject.toString();
272
- this.logger.emit('mongodb-oidc-plugin:outbound-http-request', { url });
273
- (0, util_1.validateSecureHTTPUrl)(url, '<generic>');
274
- const { customHttpOptions } = this.options;
275
- if (customHttpOptions && typeof customHttpOptions === 'object')
276
- return customHttpOptions;
277
- else
278
- return customHttpOptions?.(url, options) ?? {};
279
- };
280
- class CustomIssuer extends openid_client_2.Issuer {
281
- constructor(...params) {
282
- super(...params);
283
- this[_a] = oidcPluginCustomHttpOptionsProvider;
284
- this.Client[openid_client_1.custom.http_options] = oidcPluginCustomHttpOptionsProvider;
285
- this.Client.prototype[openid_client_1.custom.http_options] =
286
- oidcPluginCustomHttpOptionsProvider;
287
- }
288
- static async discover(issuer) {
289
- return new this((await super.discover(issuer)).metadata);
284
+ fetch = async (url, init) => {
285
+ this.logger.emit('mongodb-oidc-plugin:outbound-http-request', { url });
286
+ if (this.options.customFetch) {
287
+ return await this.options.customFetch(url, init);
288
+ }
289
+ const options = typeof this.options.customHttpOptions === 'function'
290
+ ? this.options.customHttpOptions(url, {})
291
+ : this.options.customHttpOptions;
292
+ // Same comments as in getDefaultOpenBrowser() apply regarding the import/eval fallbacks here.
293
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
294
+ let fetch;
295
+ try {
296
+ fetch = (await Promise.resolve().then(() => __importStar(require('node-fetch')))).default;
297
+ }
298
+ catch (err) {
299
+ if (err &&
300
+ typeof err === 'object' &&
301
+ 'code' in err &&
302
+ err.code === 'ERR_REQUIRE_ESM' &&
303
+ typeof __webpack_require__ === 'undefined') {
304
+ fetch = (await eval('import("node-fetch")')).default;
290
305
  }
291
- static async webfinger(input) {
292
- return new this((await super.discover(input)).metadata);
306
+ else {
307
+ throw err;
293
308
  }
294
309
  }
295
- _a = openid_client_1.custom.http_options, _b = openid_client_1.custom.http_options;
296
- CustomIssuer[_b] = oidcPluginCustomHttpOptionsProvider;
297
- return CustomIssuer;
298
- }
299
- async getOIDCClient(state, redirectURIs) {
310
+ const AgentClass = new URL(url).protocol === 'https:' ? https_1.Agent : http_1.Agent;
311
+ return (await fetch(url, {
312
+ ...init,
313
+ agent: options?.agent ?? new AgentClass(options),
314
+ ...options,
315
+ headers: { ...options?.headers },
316
+ // TS is not convinced that node-fetch and built-in fetch are compatible enough
317
+ }));
318
+ };
319
+ async getOIDCClientConfiguration(state, redirectURI) {
300
320
  const serverMetadata = state.serverOIDCMetadata;
301
- const makeScope = (issuer) => [
321
+ const makeScope = (idpMetadata) => [
302
322
  ...new Set([
303
- ...this.getSupportedDefaultScopes(issuer),
323
+ ...this.getSupportedDefaultScopes(idpMetadata),
304
324
  ...(serverMetadata.requestScopes ?? []),
305
325
  ]),
306
326
  ].join(' ');
307
- if (state.client) {
327
+ if (state.config) {
328
+ const config = new openid_client_1.Configuration(state.config.serverMetadata(), state.config.clientMetadata().client_id, {
329
+ ...state.config.clientMetadata(),
330
+ redirect_uri: redirectURI,
331
+ });
332
+ if ((0, util_1.validateSecureHTTPUrl)(config.serverMetadata().issuer, 'issuer') ===
333
+ 'http-allowed') {
334
+ (0, openid_client_1.allowInsecureRequests)(config);
335
+ }
308
336
  return {
309
- scope: makeScope(state.client.issuer),
310
- issuer: state.client.issuer,
311
- // need to re-create Client here because redirect_uris might
312
- // differ between calls to this method
313
- client: new state.client.issuer.Client({
314
- ...state.client.metadata,
315
- redirect_uris: redirectURIs,
316
- }),
337
+ scope: makeScope(config.serverMetadata()),
338
+ config,
317
339
  };
318
340
  }
319
- (0, util_1.validateSecureHTTPUrl)(serverMetadata.issuer, 'issuer');
320
- let issuer;
341
+ const httpAllowed = (0, util_1.validateSecureHTTPUrl)(serverMetadata.issuer, 'issuer');
342
+ const discoveryOptions = {
343
+ [openid_client_1.customFetch]: this.fetch,
344
+ execute: httpAllowed === 'http-allowed' ? [openid_client_1.allowInsecureRequests] : [],
345
+ };
346
+ let config;
321
347
  try {
322
- issuer = await this.getIssuerClass().discover(serverMetadata.issuer);
348
+ config = await (0, openid_client_1.discovery)(new URL(serverMetadata.issuer), serverMetadata.clientId, {
349
+ redirect_uri: redirectURI,
350
+ }, (0, openid_client_1.None)(), discoveryOptions);
351
+ // NB: The fact that `customFetch` is transfered from `discoveryOptions` to `config`
352
+ // is tested by our unit tests when they verify that all outgoing HTTP calls are logged.
323
353
  }
324
354
  catch (err) {
325
355
  // openid-client just forwards the raw Node.js HTTP error, we'll want to
326
356
  // at least include the target URL here
327
357
  throw new types_1.MongoDBOIDCError(`Unable to fetch issuer metadata for ${JSON.stringify(serverMetadata.issuer)}: ${(0, util_1.messageFromError)(err)}`, {
328
358
  cause: err,
359
+ codeName: 'IssuerMetadataDiscoveryFailed',
329
360
  });
330
361
  }
331
- (0, util_1.validateSecureHTTPUrl)(issuer.metadata.authorization_endpoint, 'authorization_endpoint');
332
- (0, util_1.validateSecureHTTPUrl)(issuer.metadata.device_authorization_endpoint, 'device_authorization_endpoint');
333
- (0, util_1.validateSecureHTTPUrl)(issuer.metadata.token_endpoint, 'token_endpoint');
334
- (0, util_1.validateSecureHTTPUrl)(issuer.metadata.jwks_uri, 'jwks_uri');
335
- const client = new issuer.Client({
336
- client_id: serverMetadata.clientId,
337
- redirect_uris: redirectURIs,
338
- response_types: ['code'],
339
- token_endpoint_auth_method: 'none',
340
- });
341
- state.client = client;
362
+ (0, util_1.validateSecureHTTPUrl)(config.serverMetadata().authorization_endpoint, 'authorization_endpoint');
363
+ (0, util_1.validateSecureHTTPUrl)(config.serverMetadata().device_authorization_endpoint, 'device_authorization_endpoint');
364
+ (0, util_1.validateSecureHTTPUrl)(config.serverMetadata().token_endpoint, 'token_endpoint');
365
+ (0, util_1.validateSecureHTTPUrl)(config.serverMetadata().jwks_uri, 'jwks_uri');
366
+ state.config = config;
342
367
  return {
343
- scope: makeScope(issuer),
344
- issuer,
345
- client,
368
+ scope: makeScope(config.serverMetadata()),
369
+ config,
346
370
  };
347
371
  }
348
372
  async openBrowser(options) {
@@ -354,14 +378,14 @@ class MongoDBOIDCPluginImpl {
354
378
  // URL has this format.
355
379
  new URL(options.url);
356
380
  if (!/^[a-zA-Z0-9%/:;_.,=@-]+$/.test(options.url)) {
357
- throw new types_1.MongoDBOIDCError(`Unexpected format for internally generated URL: '${options.url}'`);
381
+ throw new types_1.MongoDBOIDCError(`Unexpected format for internally generated URL: '${options.url}'`, { codeName: 'GeneratedUrlInvalidForOpenBrowserCommand' });
358
382
  }
359
383
  this.logger.emit('mongodb-oidc-plugin:open-browser', {
360
384
  customOpener: !!this.options.openBrowser,
361
385
  });
362
386
  if (this.options.openBrowser === false) {
363
387
  // We should never really get to this point
364
- throw new types_1.MongoDBOIDCError('Cannot open browser if `openBrowser` is false');
388
+ throw new types_1.MongoDBOIDCError('Cannot open browser if `openBrowser` is false', { codeName: 'OpenBrowserDisabled' });
365
389
  }
366
390
  if (typeof this.options.openBrowser === 'function') {
367
391
  return await this.options.openBrowser(options);
@@ -380,13 +404,15 @@ class MongoDBOIDCPluginImpl {
380
404
  child.unref();
381
405
  return child;
382
406
  }
383
- throw new types_1.MongoDBOIDCError('Unknown format for `openBrowser`');
407
+ throw new types_1.MongoDBOIDCError('Unknown format for `openBrowser`', {
408
+ codeName: 'OpenBrowserOptionFormatUnknown',
409
+ });
384
410
  }
385
411
  async notifyDeviceFlow(deviceFlowInformation) {
386
412
  (0, util_1.validateSecureHTTPUrl)(deviceFlowInformation.verificationUrl, 'verificationUrl');
387
413
  if (!this.options.notifyDeviceFlow) {
388
414
  // Should never happen.
389
- throw new types_1.MongoDBOIDCError('notifyDeviceFlow() requested but not provided');
415
+ throw new types_1.MongoDBOIDCError('notifyDeviceFlow() requested but not provided', { codeName: 'DeviceFlowNotEnabled' });
390
416
  }
391
417
  this.logger.emit('mongodb-oidc-plugin:notify-device-flow');
392
418
  await this.options.notifyDeviceFlow(deviceFlowInformation);
@@ -398,18 +424,22 @@ class MongoDBOIDCPluginImpl {
398
424
  // for client A, the token expires before it is requested again by client A,
399
425
  // then the plugin is passed to client B which requests a token, and we
400
426
  // receive mismatching tokens for different users or different audiences.
401
- if (!tokenSet.id_token &&
427
+ if (!tokenSet.idToken &&
402
428
  state.lastIdTokenClaims &&
403
429
  !state.lastIdTokenClaims.noIdToken) {
404
- throw new types_1.MongoDBOIDCError(`ID token expected, but not found. Expected claims: ${JSON.stringify(state.lastIdTokenClaims)}`);
430
+ throw new types_1.MongoDBOIDCError(`ID token expected, but not found. Expected claims: ${JSON.stringify(state.lastIdTokenClaims)}`, { codeName: 'IDTokenClaimsMismatchTokenMissing' });
405
431
  }
406
- if (tokenSet.id_token &&
432
+ if (tokenSet.idToken &&
407
433
  state.lastIdTokenClaims &&
408
434
  state.lastIdTokenClaims.noIdToken) {
409
- throw new types_1.MongoDBOIDCError(`Unexpected ID token received.`);
435
+ throw new types_1.MongoDBOIDCError(`Unexpected ID token received.`, {
436
+ codeName: 'IDTokenClaimsMismatchTokenUnexpectedlyPresent',
437
+ });
410
438
  }
411
- if (tokenSet.id_token) {
412
- const idTokenClaims = tokenSet.claims();
439
+ if (tokenSet.idToken) {
440
+ const idTokenClaims = tokenSet.idTokenClaims;
441
+ if (!idTokenClaims)
442
+ throw new types_1.MongoDBOIDCError('Internal error: id_token set but claims() unavailable', { codeName: 'IDTokenClaimsUnavailable' });
413
443
  if (state.lastIdTokenClaims && !state.lastIdTokenClaims.noIdToken) {
414
444
  for (const claim of ['aud', 'sub']) {
415
445
  const normalize = (value) => {
@@ -418,7 +448,7 @@ class MongoDBOIDCPluginImpl {
418
448
  const knownClaim = normalize(state.lastIdTokenClaims[claim]);
419
449
  const newClaim = normalize(idTokenClaims[claim]);
420
450
  if (knownClaim !== newClaim) {
421
- throw new types_1.MongoDBOIDCError(`Unexpected '${claim}' field in id token: Expected ${knownClaim}, saw ${newClaim}`);
451
+ throw new types_1.MongoDBOIDCError(`Unexpected '${claim}' field in id token: Expected ${knownClaim}, saw ${newClaim}`, { codeName: 'IDTokenClaimsMismatchClaimMismatch' });
422
452
  }
423
453
  }
424
454
  }
@@ -431,7 +461,7 @@ class MongoDBOIDCPluginImpl {
431
461
  state.lastIdTokenClaims = { noIdToken: true };
432
462
  this.logger.emit('mongodb-oidc-plugin:missing-id-token');
433
463
  }
434
- const refreshTokenId = (0, util_1.getRefreshTokenId)(tokenSet.refresh_token);
464
+ const refreshTokenId = (0, util_1.getRefreshTokenId)(tokenSet.refreshToken);
435
465
  const updateId = updateIdCounter++;
436
466
  const timerDuration = automaticRefreshTimeoutMS(tokenSet, this.options.passIdTokenAsAccessToken);
437
467
  // Use `.call()` because in browsers, `setTimeout()` requires that it is called
@@ -453,7 +483,7 @@ class MongoDBOIDCPluginImpl {
453
483
  this.logger.emit('mongodb-oidc-plugin:refresh-skipped', {
454
484
  triggeringUpdateId: updateId,
455
485
  expectedRefreshToken: refreshTokenId,
456
- actualRefreshToken: (0, util_1.getRefreshTokenId)(state.currentTokenSet?.set?.refresh_token),
486
+ actualRefreshToken: (0, util_1.getRefreshTokenId)(state.currentTokenSet?.set?.refreshToken),
457
487
  });
458
488
  return false;
459
489
  }
@@ -462,15 +492,17 @@ class MongoDBOIDCPluginImpl {
462
492
  triggeringUpdateId: updateId,
463
493
  refreshToken: refreshTokenId,
464
494
  });
465
- const { client } = await this.getOIDCClient(state);
466
- const refreshedTokens = await client.refresh(tokenSet);
495
+ if (!tokenSet.refreshToken)
496
+ return false;
497
+ const { config } = await this.getOIDCClientConfiguration(state);
498
+ const refreshedTokens = await (0, openid_client_1.refreshTokenGrant)(config, tokenSet.refreshToken);
467
499
  // Check again to avoid race conditions.
468
500
  if (state.currentTokenSet?.set === tokenSet) {
469
501
  this.logger.emit('mongodb-oidc-plugin:refresh-succeeded', {
470
502
  triggeringUpdateId: updateId,
471
503
  refreshToken: refreshTokenId,
472
504
  });
473
- this.updateStateWithTokenSet(state, refreshedTokens);
505
+ this.updateStateWithTokenSet(state, new util_1.TokenSet(refreshedTokens));
474
506
  return true;
475
507
  }
476
508
  }
@@ -490,14 +522,15 @@ class MongoDBOIDCPluginImpl {
490
522
  this.logger.emit('mongodb-oidc-plugin:state-updated', {
491
523
  updateId,
492
524
  timerDuration,
493
- tokenSetId: (0, util_1.getStableTokenSetId)(tokenSet),
525
+ tokenSetId: tokenSet.stableId(),
494
526
  });
495
527
  }
528
+ static defaultRedirectURI = 'http://localhost:27097/redirect';
496
529
  async authorizationCodeFlow(state, signal) {
497
530
  const configuredRedirectURI = this.options.redirectURI ?? MongoDBOIDCPluginImpl.defaultRedirectURI;
498
- const codeVerifier = openid_client_2.generators.codeVerifier();
499
- const codeChallenge = openid_client_2.generators.codeChallenge(codeVerifier);
500
- const oidcStateParam = (await (0, util_2.promisify)(crypto_1.randomBytes)(16)).toString('hex');
531
+ const codeVerifier = (0, openid_client_1.randomPKCECodeVerifier)();
532
+ const codeChallenge = await (0, openid_client_1.calculatePKCECodeChallenge)(codeVerifier);
533
+ const oidcStateParam = (0, openid_client_1.randomState)();
501
534
  const server = new rfc_8252_http_server_1.RFC8252HTTPServer({
502
535
  redirectUrl: configuredRedirectURI,
503
536
  logger: this.logger,
@@ -506,11 +539,11 @@ class MongoDBOIDCPluginImpl {
506
539
  });
507
540
  let paramsUrl = '';
508
541
  let scope;
509
- let client;
542
+ let config;
510
543
  let actualRedirectURI;
511
544
  const nonce = this.options.skipNonceInAuthCodeRequest
512
545
  ? undefined
513
- : openid_client_2.generators.nonce();
546
+ : (0, openid_client_1.randomNonce)();
514
547
  try {
515
548
  await (0, util_1.withAbortCheck)(signal, async ({ signalCheck, signalPromise }) => {
516
549
  // We mark the operations that we want to allow to result in a fallback
@@ -524,18 +557,17 @@ class MongoDBOIDCPluginImpl {
524
557
  signalPromise,
525
558
  ]);
526
559
  actualRedirectURI = server.listeningRedirectUrl;
527
- ({ scope, client } = await this.getOIDCClient(state, [
528
- actualRedirectURI,
529
- ]));
530
- const authCodeFlowUrl = client.authorizationUrl({
560
+ ({ scope, config } = await this.getOIDCClientConfiguration(state, actualRedirectURI));
561
+ const authCodeFlowUrl = (0, openid_client_1.buildAuthorizationUrl)(config, {
531
562
  scope,
532
563
  code_challenge: codeChallenge,
533
564
  code_challenge_method: 'S256',
534
565
  state: oidcStateParam,
535
- nonce,
566
+ ...(nonce ? { nonce } : {}),
567
+ redirect_uri: actualRedirectURI,
536
568
  });
537
569
  (0, util_1.validateSecureHTTPUrl)(authCodeFlowUrl, 'authCodeFlowUrl');
538
- const { localUrl, onAccessed: onLocalUrlAccessed } = await server.addRedirect(authCodeFlowUrl);
570
+ const { localUrl, onAccessed: onLocalUrlAccessed } = await server.addRedirect(authCodeFlowUrl.toString());
539
571
  signalCheck();
540
572
  // Handle errors from opening a browser but do not await the Promise
541
573
  // in case it only resolves when the browser exits (which is the case
@@ -547,10 +579,10 @@ class MongoDBOIDCPluginImpl {
547
579
  const extraErrorInfo = () => browserHandle?.spawnargs
548
580
  ? ` (${JSON.stringify(browserHandle.spawnargs)})`
549
581
  : '';
550
- browserHandle?.once('error', (err) => reject(new types_1.MongoDBOIDCError(`Opening browser failed with '${(0, util_1.messageFromError)(err)}'${extraErrorInfo()}`, { cause: err })));
582
+ browserHandle?.once('error', (err) => reject(new types_1.MongoDBOIDCError(`Opening browser failed with '${(0, util_1.messageFromError)(err)}'${extraErrorInfo()}`, { cause: err, codeName: 'BrowserOpenFailedSpawnError' })));
551
583
  browserHandle?.once('exit', (code) => {
552
584
  if (code !== 0)
553
- reject(new types_1.MongoDBOIDCError(`Opening browser failed with exit code ${code}${extraErrorInfo()}`));
585
+ reject(new types_1.MongoDBOIDCError(`Opening browser failed with exit code ${code}${extraErrorInfo()}`, { codeName: 'BrowserOpenFailedNonZeroExit' }));
554
586
  });
555
587
  })
556
588
  .catch(reject);
@@ -558,7 +590,9 @@ class MongoDBOIDCPluginImpl {
558
590
  const timeout = allowFallbackIfFailed(new Promise((resolve, reject) => {
559
591
  if (this.options.openBrowserTimeout !== 0) {
560
592
  this.timers.setTimeout
561
- .call(null, () => reject(new types_1.MongoDBOIDCError('Opening browser timed out')), this.options.openBrowserTimeout ?? api_1.kDefaultOpenBrowserTimeout)
593
+ .call(null, () => reject(new types_1.MongoDBOIDCError('Opening browser timed out', {
594
+ codeName: 'BrowserOpenTimeout',
595
+ })), this.options.openBrowserTimeout ?? api_1.kDefaultOpenBrowserTimeout)
562
596
  ?.unref?.();
563
597
  }
564
598
  }));
@@ -580,20 +614,18 @@ class MongoDBOIDCPluginImpl {
580
614
  finally {
581
615
  await server.close();
582
616
  }
583
- const params = client.callbackParams(paramsUrl);
584
- const tokenSet = await client.callback(actualRedirectURI, params, {
585
- code_verifier: codeVerifier,
586
- state: oidcStateParam,
587
- nonce,
617
+ const tokenSet = await (0, openid_client_1.authorizationCodeGrant)(config, new URL(paramsUrl), {
618
+ pkceCodeVerifier: codeVerifier,
619
+ expectedState: oidcStateParam,
620
+ expectedNonce: nonce,
588
621
  });
589
- this.updateStateWithTokenSet(state, tokenSet);
622
+ this.updateStateWithTokenSet(state, new util_1.TokenSet(tokenSet));
590
623
  }
591
624
  async deviceAuthorizationFlow(state, signal) {
592
- const { scope, client } = await this.getOIDCClient(state);
625
+ const { scope, config } = await this.getOIDCClientConfiguration(state);
593
626
  await (0, util_1.withAbortCheck)(signal, async ({ signalCheck, signalPromise }) => {
594
627
  const deviceFlowHandle = await Promise.race([
595
- client.deviceAuthorization({
596
- client_id: client.metadata.client_id,
628
+ (0, openid_client_1.initiateDeviceAuthorization)(config, {
597
629
  scope,
598
630
  }),
599
631
  signalPromise,
@@ -603,8 +635,8 @@ class MongoDBOIDCPluginImpl {
603
635
  userCode: deviceFlowHandle.user_code,
604
636
  verificationUrl: deviceFlowHandle.verification_uri,
605
637
  });
606
- const tokenSet = await deviceFlowHandle.poll({ signal });
607
- this.updateStateWithTokenSet(state, tokenSet);
638
+ const tokenSet = await (0, openid_client_1.pollDeviceAuthorizationGrant)(config, deviceFlowHandle, {}, { signal });
639
+ this.updateStateWithTokenSet(state, new util_1.TokenSet(tokenSet));
608
640
  });
609
641
  }
610
642
  async initiateAuthAttempt(state, driverAbortSignal, { forceRefreshOrReauth = false } = {}) {
@@ -690,11 +722,15 @@ class MongoDBOIDCPluginImpl {
690
722
  if (error)
691
723
  throw error;
692
724
  }
693
- if (passIdTokenAsAccessToken && !state.currentTokenSet?.set?.id_token) {
694
- throw new types_1.MongoDBOIDCError('Could not retrieve valid ID token');
725
+ if (passIdTokenAsAccessToken && !state.currentTokenSet?.set?.idToken) {
726
+ throw new types_1.MongoDBOIDCError('Could not retrieve valid ID token', {
727
+ codeName: 'IDTokenMissingFromTokenSet',
728
+ });
695
729
  }
696
- else if (!state.currentTokenSet?.set?.access_token) {
697
- throw new types_1.MongoDBOIDCError('Could not retrieve valid access token');
730
+ else if (!state.currentTokenSet?.set?.accessToken) {
731
+ throw new types_1.MongoDBOIDCError('Could not retrieve valid access token', {
732
+ codeName: 'AccessTokenMissingFromTokenSet',
733
+ });
698
734
  }
699
735
  }
700
736
  catch (err) {
@@ -708,22 +744,23 @@ class MongoDBOIDCPluginImpl {
708
744
  this.options.signal?.removeEventListener('abort', optionsAbortCb);
709
745
  driverAbortSignal?.removeEventListener('abort', driverAbortCb);
710
746
  }
711
- const { token_type, expires_at, access_token, id_token, refresh_token } = state.currentTokenSet.set;
712
- const tokenSetId = (0, util_1.getStableTokenSetId)(state.currentTokenSet.set);
747
+ const { tokenType, accessToken, idToken, refreshToken } = state.currentTokenSet.set;
748
+ const expiresAt = state.currentTokenSet.set.expiresAt;
749
+ const tokenSetId = state.currentTokenSet.set.stableId();
713
750
  // We would not want to return the access token or ID token of a token set whose
714
751
  // accompanying refresh token was passed to us by the driver
715
752
  const willRetryWithForceRefreshOrReauth = !forceRefreshOrReauth &&
716
753
  !!state.discardingTokenSets?.includes(tokenSetId);
717
754
  this.logger.emit('mongodb-oidc-plugin:auth-succeeded', {
718
755
  authStateId: state.id,
719
- tokenType: token_type ?? null,
720
- refreshToken: (0, util_1.getRefreshTokenId)(refresh_token),
721
- expiresAt: expires_at ? new Date(expires_at * 1000).toISOString() : null,
756
+ tokenType: tokenType ?? null, // DPoP or Bearer
757
+ refreshToken: (0, util_1.getRefreshTokenId)(refreshToken),
758
+ expiresAt: expiresAt ? new Date(expiresAt * 1000).toISOString() : null,
722
759
  passIdTokenAsAccessToken: !!passIdTokenAsAccessToken,
723
760
  tokens: {
724
- accessToken: access_token,
725
- idToken: id_token,
726
- refreshToken: refresh_token,
761
+ accessToken: accessToken,
762
+ idToken: idToken,
763
+ refreshToken: refreshToken,
727
764
  },
728
765
  tokenSetId,
729
766
  forceRefreshOrReauth,
@@ -735,7 +772,7 @@ class MongoDBOIDCPluginImpl {
735
772
  });
736
773
  }
737
774
  return {
738
- accessToken: passIdTokenAsAccessToken ? id_token || '' : access_token,
775
+ accessToken: passIdTokenAsAccessToken ? idToken || '' : accessToken,
739
776
  refreshToken: tokenSetId,
740
777
  // Passing `expiresInSeconds: 0` results in the driver not caching the token.
741
778
  // We perform our own caching here inside the plugin, so interactions with the
@@ -753,13 +790,15 @@ class MongoDBOIDCPluginImpl {
753
790
  if (params.version !== 1) {
754
791
  throw new types_1.MongoDBOIDCError(
755
792
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
756
- `OIDC MongoDB driver protocol mismatch: unknown version ${params.version}`);
793
+ `OIDC MongoDB driver protocol mismatch: unknown version ${params.version}`, { codeName: 'ProtocolVersionMismatch' });
757
794
  }
758
795
  if (this.destroyed) {
759
- throw new types_1.MongoDBOIDCError('This OIDC plugin instance has been destroyed and is no longer active');
796
+ throw new types_1.MongoDBOIDCError('This OIDC plugin instance has been destroyed and is no longer active', { codeName: 'PluginInstanceDestroyed' });
760
797
  }
761
798
  if (!params.idpInfo) {
762
- throw new types_1.MongoDBOIDCError('No IdP information provided');
799
+ throw new types_1.MongoDBOIDCError('No IdP information provided', {
800
+ codeName: 'IdPInfoMissing',
801
+ });
763
802
  }
764
803
  const state = this.getAuthState({
765
804
  ...params.idpInfo,
@@ -777,7 +816,7 @@ class MongoDBOIDCPluginImpl {
777
816
  // If the driver called us with a refresh token, that means that its corresponding
778
817
  // access token has become invalid and we should always return a new one.
779
818
  if (params.refreshToken) {
780
- (state.discardingTokenSets ?? (state.discardingTokenSets = [])).push(params.refreshToken);
819
+ (state.discardingTokenSets ??= []).push(params.refreshToken);
781
820
  this.logger.emit('mongodb-oidc-plugin:discarding-token-set', {
782
821
  tokenSetId: params.refreshToken,
783
822
  });
@@ -827,5 +866,4 @@ class MongoDBOIDCPluginImpl {
827
866
  }
828
867
  }
829
868
  exports.MongoDBOIDCPluginImpl = MongoDBOIDCPluginImpl;
830
- MongoDBOIDCPluginImpl.defaultRedirectURI = 'http://localhost:27097/redirect';
831
869
  //# sourceMappingURL=plugin.js.map