mongodb 5.3.0 → 5.4.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.
Files changed (64) hide show
  1. package/lib/admin.js +16 -0
  2. package/lib/admin.js.map +1 -1
  3. package/lib/cmap/auth/mongo_credentials.js +29 -2
  4. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  5. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +5 -3
  6. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -1
  7. package/lib/cmap/auth/mongodb_oidc/cache.js +28 -0
  8. package/lib/cmap/auth/mongodb_oidc/cache.js.map +1 -0
  9. package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js +83 -0
  10. package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js.map +1 -0
  11. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +138 -112
  12. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
  13. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +4 -2
  14. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -1
  15. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +12 -56
  16. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_oidc.js +17 -13
  18. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  19. package/lib/cmap/connection.js +12 -12
  20. package/lib/cmap/connection.js.map +1 -1
  21. package/lib/cmap/wire_protocol/constants.js +2 -2
  22. package/lib/cmap/wire_protocol/shared.js +2 -2
  23. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  24. package/lib/collection.js +3 -0
  25. package/lib/collection.js.map +1 -1
  26. package/lib/connection_string.js +8 -0
  27. package/lib/connection_string.js.map +1 -1
  28. package/lib/db.js +16 -0
  29. package/lib/db.js.map +1 -1
  30. package/lib/mongo_client.js +15 -0
  31. package/lib/mongo_client.js.map +1 -1
  32. package/lib/mongo_logger.js +40 -6
  33. package/lib/mongo_logger.js.map +1 -1
  34. package/lib/operations/run_command.js.map +1 -1
  35. package/lib/operations/stats.js.map +1 -1
  36. package/lib/utils.js +16 -1
  37. package/lib/utils.js.map +1 -1
  38. package/mongodb.d.ts +108 -24
  39. package/package.json +2 -2
  40. package/src/admin.ts +16 -0
  41. package/src/change_stream.ts +1 -1
  42. package/src/cmap/auth/mongo_credentials.ts +35 -2
  43. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +6 -3
  44. package/src/cmap/auth/mongodb_oidc/cache.ts +27 -0
  45. package/src/cmap/auth/mongodb_oidc/callback_lock_cache.ts +107 -0
  46. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +208 -171
  47. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +5 -3
  48. package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +17 -96
  49. package/src/cmap/auth/mongodb_oidc.ts +61 -37
  50. package/src/cmap/connection.ts +13 -12
  51. package/src/cmap/wire_protocol/constants.ts +2 -2
  52. package/src/cmap/wire_protocol/shared.ts +2 -3
  53. package/src/collection.ts +3 -0
  54. package/src/connection_string.ts +11 -0
  55. package/src/db.ts +16 -0
  56. package/src/index.ts +4 -3
  57. package/src/mongo_client.ts +26 -3
  58. package/src/mongo_logger.ts +42 -6
  59. package/src/operations/run_command.ts +40 -3
  60. package/src/operations/stats.ts +11 -2
  61. package/src/utils.ts +17 -0
  62. package/lib/cmap/auth/mongodb_oidc/workflow.js +0 -3
  63. package/lib/cmap/auth/mongodb_oidc/workflow.js.map +0 -1
  64. package/src/cmap/auth/mongodb_oidc/workflow.ts +0 -21
package/mongodb.d.ts CHANGED
@@ -279,6 +279,22 @@ export declare class Admin {
279
279
  /**
280
280
  * Execute a command
281
281
  *
282
+ * The driver will ensure the following fields are attached to the command sent to the server:
283
+ * - `lsid` - sourced from an implicit session or options.session
284
+ * - `$readPreference` - defaults to primary or can be configured by options.readPreference
285
+ * - `$db` - sourced from the name of this database
286
+ *
287
+ * If the client has a serverApi setting:
288
+ * - `apiVersion`
289
+ * - `apiStrict`
290
+ * - `apiDeprecationErrors`
291
+ *
292
+ * When in a transaction:
293
+ * - `readConcern` - sourced from readConcern set on the TransactionOptions
294
+ * - `writeConcern` - sourced from writeConcern set on the TransactionOptions
295
+ *
296
+ * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value.
297
+ *
282
298
  * @param command - The command to execute
283
299
  * @param options - Optional settings for the command
284
300
  */
@@ -537,6 +553,8 @@ export declare interface AuthMechanismProperties extends Document {
537
553
  REFRESH_TOKEN_CALLBACK?: OIDCRefreshFunction;
538
554
  /** @experimental */
539
555
  PROVIDER_NAME?: 'aws';
556
+ /** @experimental */
557
+ ALLOWED_HOSTS?: string[];
540
558
  }
541
559
 
542
560
  /** @public */
@@ -1065,7 +1083,7 @@ export declare class ChangeStream<TSchema extends Document = Document, TChange e
1065
1083
  /**
1066
1084
  * Try to get the next available document from the Change Stream's cursor or `null` if an empty batch is returned
1067
1085
  */
1068
- tryNext(): Promise<Document | null>;
1086
+ tryNext(): Promise<TChange | null>;
1069
1087
  [Symbol.asyncIterator](): AsyncGenerator<TChange, void, void>;
1070
1088
  /** Is the cursor closed */
1071
1089
  get closed(): boolean;
@@ -1997,6 +2015,9 @@ export declare class Collection<TSchema extends Document = Document> {
1997
2015
  /**
1998
2016
  * Get all the collection statistics.
1999
2017
  *
2018
+ * @deprecated the `collStats` operation will be removed in the next major release. Please
2019
+ * use an aggregation pipeline with the [`$collStats`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/) stage instead
2020
+ *
2000
2021
  * @param options - Optional settings for the command
2001
2022
  */
2002
2023
  stats(options?: CollStatsOptions): Promise<CollStats>;
@@ -2125,6 +2146,8 @@ export declare interface CollectionOptions extends BSONSerializeOptions, WriteCo
2125
2146
  /* Excluded from this release type: CollectionPrivate */
2126
2147
 
2127
2148
  /**
2149
+ * @deprecated the `collStats` operation will be removed in the next major release. Please
2150
+ * use an aggregation pipeline with the [`$collStats`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/) stage instead
2128
2151
  * @public
2129
2152
  * @see https://www.mongodb.com/docs/manual/reference/command/collStats/
2130
2153
  */
@@ -2177,7 +2200,11 @@ export declare interface CollStats extends Document {
2177
2200
  scaleFactor: number;
2178
2201
  }
2179
2202
 
2180
- /** @public */
2203
+ /**
2204
+ * @public
2205
+ * @deprecated the `collStats` operation will be removed in the next major release. Please
2206
+ * use an aggregation pipeline with the [`$collStats`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/) stage instead
2207
+ */
2181
2208
  export declare interface CollStatsOptions extends CommandOperationOptions {
2182
2209
  /** Divide the returned sizes by scale value. */
2183
2210
  scale?: number;
@@ -2711,6 +2738,22 @@ export declare class Db {
2711
2738
  * @remarks
2712
2739
  * This command does not inherit options from the MongoClient.
2713
2740
  *
2741
+ * The driver will ensure the following fields are attached to the command sent to the server:
2742
+ * - `lsid` - sourced from an implicit session or options.session
2743
+ * - `$readPreference` - defaults to primary or can be configured by options.readPreference
2744
+ * - `$db` - sourced from the name of this database
2745
+ *
2746
+ * If the client has a serverApi setting:
2747
+ * - `apiVersion`
2748
+ * - `apiStrict`
2749
+ * - `apiDeprecationErrors`
2750
+ *
2751
+ * When in a transaction:
2752
+ * - `readConcern` - sourced from readConcern set on the TransactionOptions
2753
+ * - `writeConcern` - sourced from writeConcern set on the TransactionOptions
2754
+ *
2755
+ * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value.
2756
+ *
2714
2757
  * @param command - The command to run
2715
2758
  * @param options - Optional settings for the command
2716
2759
  */
@@ -3675,6 +3718,26 @@ export declare class HostAddress {
3675
3718
  };
3676
3719
  }
3677
3720
 
3721
+ /**
3722
+ * @public
3723
+ * @experimental
3724
+ */
3725
+ export declare interface IdPServerInfo {
3726
+ issuer: string;
3727
+ clientId: string;
3728
+ requestScopes?: string[];
3729
+ }
3730
+
3731
+ /**
3732
+ * @public
3733
+ * @experimental
3734
+ */
3735
+ export declare interface IdPServerResponse {
3736
+ accessToken: string;
3737
+ expiresInSeconds?: number;
3738
+ refreshToken?: string;
3739
+ }
3740
+
3678
3741
  /** @public */
3679
3742
  export declare interface IndexDescription extends Pick<CreateIndexesOptions, 'background' | 'unique' | 'partialFilterExpression' | 'sparse' | 'hidden' | 'expireAfterSeconds' | 'storageEngine' | 'version' | 'weights' | 'default_language' | 'language_override' | 'textIndexVersion' | '2dsphereIndexVersion' | 'bits' | 'min' | 'max' | 'bucketSize' | 'wildcardProjection'> {
3680
3743
  collation?: CollationOptions;
@@ -4399,7 +4462,7 @@ export declare class MongoCredentials {
4399
4462
 
4400
4463
  /** @public */
4401
4464
  export declare interface MongoCredentialsOptions {
4402
- username: string;
4465
+ username?: string;
4403
4466
  password: string;
4404
4467
  source: string;
4405
4468
  db?: string;
@@ -4943,36 +5006,24 @@ export { ObjectId }
4943
5006
  * @public
4944
5007
  * @experimental
4945
5008
  */
4946
- export declare interface OIDCMechanismServerStep1 {
4947
- authorizationEndpoint?: string;
4948
- tokenEndpoint?: string;
4949
- deviceAuthorizationEndpoint?: string;
4950
- clientId: string;
4951
- clientSecret?: string;
4952
- requestScopes?: string[];
5009
+ export declare interface OIDCCallbackContext {
5010
+ refreshToken?: string;
5011
+ timeoutSeconds?: number;
5012
+ timeoutContext?: AbortSignal;
5013
+ version: number;
4953
5014
  }
4954
5015
 
4955
5016
  /**
4956
5017
  * @public
4957
5018
  * @experimental
4958
5019
  */
4959
- export declare type OIDCRefreshFunction = (principalName: string, serverResult: OIDCMechanismServerStep1, result: OIDCRequestTokenResult, timeout: AbortSignal | number) => Promise<OIDCRequestTokenResult>;
4960
-
4961
- /**
4962
- * @public
4963
- * @experimental
4964
- */
4965
- export declare type OIDCRequestFunction = (principalName: string, serverResult: OIDCMechanismServerStep1, timeout: AbortSignal | number) => Promise<OIDCRequestTokenResult>;
5020
+ export declare type OIDCRefreshFunction = (info: IdPServerInfo, context: OIDCCallbackContext) => Promise<IdPServerResponse>;
4966
5021
 
4967
5022
  /**
4968
5023
  * @public
4969
5024
  * @experimental
4970
5025
  */
4971
- export declare interface OIDCRequestTokenResult {
4972
- accessToken: string;
4973
- expiresInSeconds?: number;
4974
- refreshToken?: string;
4975
- }
5026
+ export declare type OIDCRequestFunction = (info: IdPServerInfo, context: OIDCCallbackContext) => Promise<IdPServerResponse>;
4976
5027
 
4977
5028
  /** @public */
4978
5029
  export declare type OneOrMore<T> = T | ReadonlyArray<T>;
@@ -5352,7 +5403,37 @@ export declare interface RootFilterOperators<TSchema> extends Document {
5352
5403
  /* Excluded from this release type: RTTPingerOptions */
5353
5404
 
5354
5405
  /** @public */
5355
- export declare type RunCommandOptions = CommandOperationOptions;
5406
+ export declare type RunCommandOptions = {
5407
+ /** Specify ClientSession for this command */
5408
+ session?: ClientSession;
5409
+ /** The read preference */
5410
+ readPreference?: ReadPreferenceLike;
5411
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5412
+ willRetryWrite?: any;
5413
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5414
+ omitReadPreference?: any;
5415
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5416
+ writeConcern?: any;
5417
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5418
+ explain?: any;
5419
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5420
+ readConcern?: any;
5421
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5422
+ collation?: any;
5423
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5424
+ maxTimeMS?: any;
5425
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5426
+ comment?: any;
5427
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5428
+ retryWrites?: any;
5429
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5430
+ dbName?: any;
5431
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5432
+ authdb?: any;
5433
+ /** @deprecated This is an internal option that has undefined behavior for this API */
5434
+ noResponse?: any;
5435
+ /* Excluded from this release type: bypassPinningCheck */
5436
+ } & BSONSerializeOptions;
5356
5437
 
5357
5438
  /** @public */
5358
5439
  export declare type SchemaMember<T, V> = {
@@ -6128,7 +6209,10 @@ export declare type W = number | 'majority';
6128
6209
 
6129
6210
  /* Excluded from this release type: WaitQueueMember */
6130
6211
 
6131
- /** @public */
6212
+ /**
6213
+ * @public
6214
+ * @deprecated This type is only used for the deprecated `collStats` operation and will be removed in the next major release.
6215
+ */
6132
6216
  export declare interface WiredTigerData extends Document {
6133
6217
  LSM: {
6134
6218
  'bloom filter false positives': number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongodb",
3
- "version": "5.3.0",
3
+ "version": "5.4.0",
4
4
  "description": "The official MongoDB driver for Node.js",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -127,7 +127,7 @@
127
127
  "check:atlas": "mocha --config test/manual/mocharc.json test/manual/atlas_connectivity.test.js",
128
128
  "check:adl": "mocha --config test/mocha_mongodb.json test/manual/atlas-data-lake-testing",
129
129
  "check:aws": "nyc mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_aws.test.ts",
130
- "check:oidc": "mocha --config test/manual/mocharc.json test/manual/mongodb_oidc.prose.test.ts",
130
+ "check:oidc": "mocha --config test/mocha_mongodb.json test/manual/mongodb_oidc.prose.test.ts",
131
131
  "check:ocsp": "mocha --config test/manual/mocharc.json test/manual/ocsp_support.test.js",
132
132
  "check:kerberos": "nyc mocha --config test/manual/mocharc.json test/manual/kerberos.test.ts",
133
133
  "check:tls": "mocha --config test/manual/mocharc.json test/manual/tls_support.test.js",
package/src/admin.ts CHANGED
@@ -54,6 +54,22 @@ export class Admin {
54
54
  /**
55
55
  * Execute a command
56
56
  *
57
+ * The driver will ensure the following fields are attached to the command sent to the server:
58
+ * - `lsid` - sourced from an implicit session or options.session
59
+ * - `$readPreference` - defaults to primary or can be configured by options.readPreference
60
+ * - `$db` - sourced from the name of this database
61
+ *
62
+ * If the client has a serverApi setting:
63
+ * - `apiVersion`
64
+ * - `apiStrict`
65
+ * - `apiDeprecationErrors`
66
+ *
67
+ * When in a transaction:
68
+ * - `readConcern` - sourced from readConcern set on the TransactionOptions
69
+ * - `writeConcern` - sourced from writeConcern set on the TransactionOptions
70
+ *
71
+ * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value.
72
+ *
57
73
  * @param command - The command to execute
58
74
  * @param options - Optional settings for the command
59
75
  */
@@ -700,7 +700,7 @@ export class ChangeStream<
700
700
  /**
701
701
  * Try to get the next available document from the Change Stream's cursor or `null` if an empty batch is returned
702
702
  */
703
- async tryNext(): Promise<Document | null> {
703
+ async tryNext(): Promise<TChange | null> {
704
704
  this._setIsIterator();
705
705
  // Change streams must resume indefinitely while each resume event succeeds.
706
706
  // This loop continues until either a change event is received or until a resume attempt
@@ -30,6 +30,18 @@ function getDefaultAuthMechanism(hello?: Document): AuthMechanism {
30
30
  return AuthMechanism.MONGODB_CR;
31
31
  }
32
32
 
33
+ const ALLOWED_HOSTS_ERROR = 'Auth mechanism property ALLOWED_HOSTS must be an array of strings.';
34
+
35
+ /** @internal */
36
+ export const DEFAULT_ALLOWED_HOSTS = [
37
+ '*.mongodb.net',
38
+ '*.mongodb-dev.net',
39
+ '*.mongodbgov.net',
40
+ 'localhost',
41
+ '127.0.0.1',
42
+ '::1'
43
+ ];
44
+
33
45
  /** @public */
34
46
  export interface AuthMechanismProperties extends Document {
35
47
  SERVICE_HOST?: string;
@@ -43,11 +55,13 @@ export interface AuthMechanismProperties extends Document {
43
55
  REFRESH_TOKEN_CALLBACK?: OIDCRefreshFunction;
44
56
  /** @experimental */
45
57
  PROVIDER_NAME?: 'aws';
58
+ /** @experimental */
59
+ ALLOWED_HOSTS?: string[];
46
60
  }
47
61
 
48
62
  /** @public */
49
63
  export interface MongoCredentialsOptions {
50
- username: string;
64
+ username?: string;
51
65
  password: string;
52
66
  source: string;
53
67
  db?: string;
@@ -72,7 +86,7 @@ export class MongoCredentials {
72
86
  readonly mechanismProperties: AuthMechanismProperties;
73
87
 
74
88
  constructor(options: MongoCredentialsOptions) {
75
- this.username = options.username;
89
+ this.username = options.username ?? '';
76
90
  this.password = options.password;
77
91
  this.source = options.source;
78
92
  if (!this.source && options.db) {
@@ -101,6 +115,13 @@ export class MongoCredentials {
101
115
  }
102
116
  }
103
117
 
118
+ if (this.mechanism === AuthMechanism.MONGODB_OIDC && !this.mechanismProperties.ALLOWED_HOSTS) {
119
+ this.mechanismProperties = {
120
+ ...this.mechanismProperties,
121
+ ALLOWED_HOSTS: DEFAULT_ALLOWED_HOSTS
122
+ };
123
+ }
124
+
104
125
  Object.freeze(this.mechanismProperties);
105
126
  Object.freeze(this);
106
127
  }
@@ -181,6 +202,18 @@ export class MongoCredentials {
181
202
  `Either a PROVIDER_NAME or a REQUEST_TOKEN_CALLBACK must be specified for mechanism '${this.mechanism}'.`
182
203
  );
183
204
  }
205
+
206
+ if (this.mechanismProperties.ALLOWED_HOSTS) {
207
+ const hosts = this.mechanismProperties.ALLOWED_HOSTS;
208
+ if (!Array.isArray(hosts)) {
209
+ throw new MongoInvalidArgumentError(ALLOWED_HOSTS_ERROR);
210
+ }
211
+ for (const host of hosts) {
212
+ if (typeof host !== 'string') {
213
+ throw new MongoInvalidArgumentError(ALLOWED_HOSTS_ERROR);
214
+ }
215
+ }
216
+ }
184
217
  }
185
218
 
186
219
  if (AUTH_MECHS_AUTH_SRC_EXTERNAL.has(this.mechanism)) {
@@ -1,8 +1,11 @@
1
- import { readFile } from 'fs/promises';
1
+ import * as fs from 'fs';
2
2
 
3
3
  import { MongoAWSError } from '../../../error';
4
4
  import { ServiceWorkflow } from './service_workflow';
5
5
 
6
+ /** Error for when the token is missing in the environment. */
7
+ const TOKEN_MISSING_ERROR = 'AWS_WEB_IDENTITY_TOKEN_FILE must be set in the environment.';
8
+
6
9
  /**
7
10
  * Device workflow implementation for AWS.
8
11
  *
@@ -19,8 +22,8 @@ export class AwsServiceWorkflow extends ServiceWorkflow {
19
22
  async getToken(): Promise<string> {
20
23
  const tokenFile = process.env.AWS_WEB_IDENTITY_TOKEN_FILE;
21
24
  if (!tokenFile) {
22
- throw new MongoAWSError('AWS_WEB_IDENTITY_TOKEN_FILE must be set in the environment.');
25
+ throw new MongoAWSError(TOKEN_MISSING_ERROR);
23
26
  }
24
- return readFile(tokenFile, 'utf8');
27
+ return fs.promises.readFile(tokenFile, 'utf8');
25
28
  }
26
29
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Base class for OIDC caches.
3
+ */
4
+ export abstract class Cache<T> {
5
+ entries: Map<string, T>;
6
+
7
+ /**
8
+ * Create a new cache.
9
+ */
10
+ constructor() {
11
+ this.entries = new Map<string, T>();
12
+ }
13
+
14
+ /**
15
+ * Clear the cache.
16
+ */
17
+ clear() {
18
+ this.entries.clear();
19
+ }
20
+
21
+ /**
22
+ * Create a cache key from the address and username.
23
+ */
24
+ cacheKey(address: string, username: string, callbackHash: string): string {
25
+ return JSON.stringify([address, username, callbackHash]);
26
+ }
27
+ }
@@ -0,0 +1,107 @@
1
+ import { MongoInvalidArgumentError } from '../../../error';
2
+ import type { Connection } from '../../connection';
3
+ import type { MongoCredentials } from '../mongo_credentials';
4
+ import type {
5
+ IdPServerInfo,
6
+ IdPServerResponse,
7
+ OIDCCallbackContext,
8
+ OIDCRefreshFunction,
9
+ OIDCRequestFunction
10
+ } from '../mongodb_oidc';
11
+ import { Cache } from './cache';
12
+
13
+ /** Error message for when request callback is missing. */
14
+ const REQUEST_CALLBACK_REQUIRED_ERROR =
15
+ 'Auth mechanism property REQUEST_TOKEN_CALLBACK is required.';
16
+ /* Counter for function "hashes".*/
17
+ let FN_HASH_COUNTER = 0;
18
+ /* No function present function */
19
+ const NO_FUNCTION: OIDCRequestFunction = async () => ({ accessToken: 'test' });
20
+ /* The map of function hashes */
21
+ const FN_HASHES = new WeakMap<OIDCRequestFunction | OIDCRefreshFunction, number>();
22
+ /* Put the no function hash in the map. */
23
+ FN_HASHES.set(NO_FUNCTION, FN_HASH_COUNTER);
24
+
25
+ /**
26
+ * An entry of callbacks in the cache.
27
+ */
28
+ interface CallbacksEntry {
29
+ requestCallback: OIDCRequestFunction;
30
+ refreshCallback?: OIDCRefreshFunction;
31
+ callbackHash: string;
32
+ }
33
+
34
+ /**
35
+ * A cache of request and refresh callbacks per server/user.
36
+ */
37
+ export class CallbackLockCache extends Cache<CallbacksEntry> {
38
+ /**
39
+ * Get the callbacks for the connection and credentials. If an entry does not
40
+ * exist a new one will get set.
41
+ */
42
+ getCallbacks(connection: Connection, credentials: MongoCredentials): CallbacksEntry {
43
+ const requestCallback = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
44
+ const refreshCallback = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
45
+ if (!requestCallback) {
46
+ throw new MongoInvalidArgumentError(REQUEST_CALLBACK_REQUIRED_ERROR);
47
+ }
48
+ const callbackHash = hashFunctions(requestCallback, refreshCallback);
49
+ const key = this.cacheKey(connection.address, credentials.username, callbackHash);
50
+ const entry = this.entries.get(key);
51
+ if (entry) {
52
+ return entry;
53
+ }
54
+ return this.setCallbacks(key, callbackHash, requestCallback, refreshCallback);
55
+ }
56
+
57
+ /**
58
+ * Set locked callbacks on for connection and credentials.
59
+ */
60
+ private setCallbacks(
61
+ key: string,
62
+ callbackHash: string,
63
+ requestCallback: OIDCRequestFunction,
64
+ refreshCallback?: OIDCRefreshFunction
65
+ ): CallbacksEntry {
66
+ const entry = {
67
+ requestCallback: withLock(requestCallback),
68
+ refreshCallback: refreshCallback ? withLock(refreshCallback) : undefined,
69
+ callbackHash: callbackHash
70
+ };
71
+ this.entries.set(key, entry);
72
+ return entry;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Ensure the callback is only executed one at a time.
78
+ */
79
+ function withLock(callback: OIDCRequestFunction | OIDCRefreshFunction) {
80
+ let lock: Promise<any> = Promise.resolve();
81
+ return async (info: IdPServerInfo, context: OIDCCallbackContext): Promise<IdPServerResponse> => {
82
+ await lock;
83
+ lock = lock.then(() => callback(info, context));
84
+ return lock;
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Get the hash string for the request and refresh functions.
90
+ */
91
+ function hashFunctions(requestFn: OIDCRequestFunction, refreshFn?: OIDCRefreshFunction): string {
92
+ let requestHash = FN_HASHES.get(requestFn);
93
+ let refreshHash = FN_HASHES.get(refreshFn ?? NO_FUNCTION);
94
+ if (requestHash == null) {
95
+ // Create a new one for the function and put it in the map.
96
+ FN_HASH_COUNTER++;
97
+ requestHash = FN_HASH_COUNTER;
98
+ FN_HASHES.set(requestFn, FN_HASH_COUNTER);
99
+ }
100
+ if (refreshHash == null && refreshFn) {
101
+ // Create a new one for the function and put it in the map.
102
+ FN_HASH_COUNTER++;
103
+ refreshHash = FN_HASH_COUNTER;
104
+ FN_HASHES.set(refreshFn, FN_HASH_COUNTER);
105
+ }
106
+ return `${requestHash}-${refreshHash}`;
107
+ }