mongodb 7.5.0 → 7.6.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 (107) hide show
  1. package/README.md +9 -1
  2. package/lib/bulk/common.js +27 -5
  3. package/lib/bulk/common.js.map +1 -1
  4. package/lib/bulk/ordered.js +25 -7
  5. package/lib/bulk/ordered.js.map +1 -1
  6. package/lib/bulk/unordered.js +25 -7
  7. package/lib/bulk/unordered.js.map +1 -1
  8. package/lib/client-side-encryption/auto_encrypter.js +8 -2
  9. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  10. package/lib/client-side-encryption/client_encryption.js +12 -4
  11. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  12. package/lib/client-side-encryption/kms_options.js +3 -0
  13. package/lib/client-side-encryption/kms_options.js.map +1 -0
  14. package/lib/client-side-encryption/state_machine.js +54 -7
  15. package/lib/client-side-encryption/state_machine.js.map +1 -1
  16. package/lib/cmap/auth/gssapi.js +2 -1
  17. package/lib/cmap/auth/gssapi.js.map +1 -1
  18. package/lib/cmap/auth/mongo_credentials.js +0 -1
  19. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  20. package/lib/cmap/auth/mongodb_aws.js +0 -3
  21. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  22. package/lib/cmap/command_monitoring_events.js +6 -7
  23. package/lib/cmap/command_monitoring_events.js.map +1 -1
  24. package/lib/cmap/commands.js +13 -0
  25. package/lib/cmap/commands.js.map +1 -1
  26. package/lib/cmap/connect.js +2 -2
  27. package/lib/cmap/connect.js.map +1 -1
  28. package/lib/cmap/connection.js +3 -1
  29. package/lib/cmap/connection.js.map +1 -1
  30. package/lib/cmap/handshake/client_metadata.js +2 -1
  31. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  32. package/lib/cmap/wire_protocol/constants.js +2 -2
  33. package/lib/connection_string.js +4 -0
  34. package/lib/connection_string.js.map +1 -1
  35. package/lib/cursor/abstract_cursor.js.map +1 -1
  36. package/lib/error.js +7 -11
  37. package/lib/error.js.map +1 -1
  38. package/lib/gridfs/download.js +1 -1
  39. package/lib/gridfs/download.js.map +1 -1
  40. package/lib/index.js.map +1 -1
  41. package/lib/operations/command.js.map +1 -1
  42. package/lib/operations/delete.js +7 -10
  43. package/lib/operations/delete.js.map +1 -1
  44. package/lib/operations/execute_operation.js +29 -2
  45. package/lib/operations/execute_operation.js.map +1 -1
  46. package/lib/operations/find_and_modify.js +1 -5
  47. package/lib/operations/find_and_modify.js.map +1 -1
  48. package/lib/operations/get_more.js +2 -3
  49. package/lib/operations/get_more.js.map +1 -1
  50. package/lib/operations/indexes.js +3 -9
  51. package/lib/operations/indexes.js.map +1 -1
  52. package/lib/operations/insert.js +6 -2
  53. package/lib/operations/insert.js.map +1 -1
  54. package/lib/operations/list_collections.js +2 -3
  55. package/lib/operations/list_collections.js.map +1 -1
  56. package/lib/operations/list_databases.js +2 -2
  57. package/lib/operations/list_databases.js.map +1 -1
  58. package/lib/operations/update.js +6 -2
  59. package/lib/operations/update.js.map +1 -1
  60. package/lib/runtime_adapters.js +15 -16
  61. package/lib/runtime_adapters.js.map +1 -1
  62. package/lib/sdam/server.js +1 -1
  63. package/lib/sdam/server.js.map +1 -1
  64. package/lib/sessions.js +2 -2
  65. package/lib/sessions.js.map +1 -1
  66. package/lib/utils.js +1 -3
  67. package/lib/utils.js.map +1 -1
  68. package/mongodb.d.ts +77 -20
  69. package/package.json +3 -2
  70. package/src/bulk/common.ts +37 -5
  71. package/src/bulk/ordered.ts +23 -7
  72. package/src/bulk/unordered.ts +23 -8
  73. package/src/client-side-encryption/auto_encrypter.ts +19 -3
  74. package/src/client-side-encryption/client_encryption.ts +29 -9
  75. package/src/client-side-encryption/kms_options.ts +93 -0
  76. package/src/client-side-encryption/state_machine.ts +68 -47
  77. package/src/cmap/auth/gssapi.ts +3 -4
  78. package/src/cmap/auth/mongo_credentials.ts +0 -1
  79. package/src/cmap/auth/mongodb_aws.ts +2 -12
  80. package/src/cmap/command_monitoring_events.ts +9 -10
  81. package/src/cmap/commands.ts +17 -0
  82. package/src/cmap/connect.ts +3 -3
  83. package/src/cmap/connection.ts +4 -2
  84. package/src/cmap/handshake/client_metadata.ts +2 -1
  85. package/src/cmap/wire_protocol/constants.ts +2 -2
  86. package/src/connection_string.ts +4 -0
  87. package/src/cursor/abstract_cursor.ts +0 -5
  88. package/src/error.ts +7 -19
  89. package/src/gridfs/download.ts +1 -1
  90. package/src/index.ts +7 -6
  91. package/src/mongo_client.ts +1 -1
  92. package/src/operations/command.ts +0 -5
  93. package/src/operations/delete.ts +17 -15
  94. package/src/operations/execute_operation.ts +29 -2
  95. package/src/operations/find_and_modify.ts +3 -15
  96. package/src/operations/get_more.ts +3 -5
  97. package/src/operations/indexes.ts +6 -16
  98. package/src/operations/insert.ts +13 -2
  99. package/src/operations/list_collections.ts +5 -6
  100. package/src/operations/list_databases.ts +3 -3
  101. package/src/operations/update.ts +9 -2
  102. package/src/operations/validate_collection.ts +1 -1
  103. package/src/runtime_adapters.ts +19 -18
  104. package/src/sdam/server.ts +1 -1
  105. package/src/sessions.ts +2 -5
  106. package/src/utils.ts +1 -3
  107. package/tsconfig.json +9 -2
@@ -17,13 +17,28 @@ export class OrderedBulkOperation extends BulkOperationBase {
17
17
  batchType: BatchType,
18
18
  document: Document | UpdateStatement | DeleteStatement
19
19
  ): this {
20
- // Get the bsonSize
21
- const bsonSize = BSON.calculateObjectSize(document, {
22
- checkKeys: false,
23
- // Since we don't know what the user selected for BSON options here,
24
- // err on the safe side, and check the size with ignoreUndefined: false.
25
- ignoreUndefined: false
26
- } as any);
20
+ // Serialize the operation once here and reuse the bytes for both the size
21
+ // check/splitting and the wire message (via a DocumentSequence). Under
22
+ // auto-encryption the command is sent as a BSON array rather than a
23
+ // document sequence, so the buffer would never be reused; there we only
24
+ // measure the size and leave `buffer` undefined so nothing is retained on
25
+ // the batch.
26
+ let buffer: Uint8Array | undefined;
27
+ let bsonSize: number;
28
+ if (this.s.usingAutoEncryption) {
29
+ bsonSize = BSON.calculateObjectSize(document, {
30
+ checkKeys: false,
31
+ ignoreUndefined: false
32
+ } as any);
33
+ } else {
34
+ const bson = this.s.bsonOptions;
35
+ buffer = BSON.serialize(document, {
36
+ checkKeys: this.s.checkKeys,
37
+ ignoreUndefined: bson.ignoreUndefined,
38
+ serializeFunctions: bson.serializeFunctions
39
+ });
40
+ bsonSize = buffer.length;
41
+ }
27
42
 
28
43
  // Throw error if the doc is bigger than the max BSON size
29
44
  if (bsonSize >= this.s.maxBsonObjectSize)
@@ -75,6 +90,7 @@ export class OrderedBulkOperation extends BulkOperationBase {
75
90
 
76
91
  this.s.currentBatch.originalIndexes.push(this.s.currentIndex);
77
92
  this.s.currentBatch.operations.push(document);
93
+ if (buffer != null) this.s.currentBatch.serializedOperations.push(buffer);
78
94
  this.s.currentBatchSize += 1;
79
95
  this.s.currentBatchSizeBytes += maxKeySize + bsonSize;
80
96
  this.s.currentIndex += 1;
@@ -31,14 +31,28 @@ export class UnorderedBulkOperation extends BulkOperationBase {
31
31
  batchType: BatchType,
32
32
  document: Document | UpdateStatement | DeleteStatement
33
33
  ): this {
34
- // Get the bsonSize
35
- const bsonSize = BSON.calculateObjectSize(document, {
36
- checkKeys: false,
37
-
38
- // Since we don't know what the user selected for BSON options here,
39
- // err on the safe side, and check the size with ignoreUndefined: false.
40
- ignoreUndefined: false
41
- } as any);
34
+ // Serialize the operation once here and reuse the bytes for both the size
35
+ // check/splitting and the wire message (via a DocumentSequence). Under
36
+ // auto-encryption the command is sent as a BSON array rather than a
37
+ // document sequence, so the buffer would never be reused; there we only
38
+ // measure the size and leave `buffer` undefined so nothing is retained on
39
+ // the batch.
40
+ let buffer: Uint8Array | undefined;
41
+ let bsonSize: number;
42
+ if (this.s.usingAutoEncryption) {
43
+ bsonSize = BSON.calculateObjectSize(document, {
44
+ checkKeys: false,
45
+ ignoreUndefined: false
46
+ } as any);
47
+ } else {
48
+ const bson = this.s.bsonOptions;
49
+ buffer = BSON.serialize(document, {
50
+ checkKeys: this.s.checkKeys,
51
+ ignoreUndefined: bson.ignoreUndefined,
52
+ serializeFunctions: bson.serializeFunctions
53
+ });
54
+ bsonSize = buffer.length;
55
+ }
42
56
 
43
57
  // Throw error if the doc is bigger than the max BSON size
44
58
  if (bsonSize >= this.s.maxBsonObjectSize) {
@@ -90,6 +104,7 @@ export class UnorderedBulkOperation extends BulkOperationBase {
90
104
  }
91
105
 
92
106
  this.s.currentBatch.operations.push(document);
107
+ if (buffer != null) this.s.currentBatch.serializedOperations.push(buffer);
93
108
  this.s.currentBatch.originalIndexes.push(this.s.currentIndex);
94
109
  this.s.currentIndex = this.s.currentIndex + 1;
95
110
 
@@ -11,6 +11,7 @@ import { type Abortable } from '../mongo_types';
11
11
  import { MongoDBCollectionNamespace } from '../utils';
12
12
  import { autoSelectSocketOptions } from './client_encryption';
13
13
  import { defaultErrorWrapper, MongoCryptInvalidArgumentError } from './errors';
14
+ import { type CSFLEKMSTlsOptions, type KMSConnectCallback } from './kms_options';
14
15
  import { MongocryptdManager } from './mongocryptd_manager';
15
16
  import {
16
17
  type CredentialProviders,
@@ -18,7 +19,7 @@ import {
18
19
  type KMSProviders,
19
20
  refreshKMSCredentials
20
21
  } from './providers';
21
- import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
22
+ import { StateMachine } from './state_machine';
22
23
 
23
24
  /** @public */
24
25
  export interface AutoEncryptionOptions {
@@ -110,6 +111,12 @@ export interface AutoEncryptionOptions {
110
111
  proxyOptions?: ProxyOptions;
111
112
  /** The TLS options to use connecting to the KMS provider */
112
113
  tlsOptions?: CSFLEKMSTlsOptions;
114
+ /**
115
+ * A callback that establishes the socket used to connect to a KMS host, e.g. to route KMS
116
+ * requests through an HTTP proxy via the HTTP CONNECT method. Mutually exclusive with `proxyOptions`.
117
+ * See {@link KMSConnectCallback} for a usage example.
118
+ */
119
+ kmsConnectCallback?: KMSConnectCallback;
113
120
  }
114
121
 
115
122
  /**
@@ -156,6 +163,7 @@ export class AutoEncrypter {
156
163
  _metaDataClient: MongoClient;
157
164
  _proxyOptions: ProxyOptions;
158
165
  _tlsOptions: CSFLEKMSTlsOptions;
166
+ _kmsConnectCallback?: KMSConnectCallback;
159
167
  _kmsProviders: KMSProviders;
160
168
  _bypassMongocryptdAndCryptShared: boolean;
161
169
  _contextCounter: number;
@@ -242,7 +250,13 @@ export class AutoEncrypter {
242
250
  this._keyVaultClient = options.keyVaultClient || client;
243
251
  this._metaDataClient = options.metadataClient || client;
244
252
  this._proxyOptions = options.proxyOptions || {};
253
+ if (this._proxyOptions.proxyHost && options.kmsConnectCallback) {
254
+ throw new MongoCryptInvalidArgumentError(
255
+ 'Cannot set both proxyOptions and kmsConnectCallback'
256
+ );
257
+ }
245
258
  this._tlsOptions = options.tlsOptions || {};
259
+ this._kmsConnectCallback = options.kmsConnectCallback;
246
260
  this._kmsProviders = options.kmsProviders || {};
247
261
  this._credentialProviders = options.credentialProviders;
248
262
 
@@ -417,7 +431,8 @@ export class AutoEncrypter {
417
431
  promoteLongs: false,
418
432
  proxyOptions: this._proxyOptions,
419
433
  tlsOptions: this._tlsOptions,
420
- socketOptions: autoSelectSocketOptions(this._client.s.options)
434
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
435
+ kmsConnectCallback: this._kmsConnectCallback
421
436
  });
422
437
 
423
438
  return deserialize(await stateMachine.execute(this, context, options), {
@@ -443,7 +458,8 @@ export class AutoEncrypter {
443
458
  ...options,
444
459
  proxyOptions: this._proxyOptions,
445
460
  tlsOptions: this._tlsOptions,
446
- socketOptions: autoSelectSocketOptions(this._client.s.options)
461
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
462
+ kmsConnectCallback: this._kmsConnectCallback
447
463
  });
448
464
 
449
465
  return await stateMachine.execute(this, context, options);
@@ -31,6 +31,11 @@ import {
31
31
  MongoCryptCreateEncryptedCollectionError,
32
32
  MongoCryptInvalidArgumentError
33
33
  } from './errors';
34
+ import {
35
+ type ClientEncryptionSocketOptions,
36
+ type CSFLEKMSTlsOptions,
37
+ type KMSConnectCallback
38
+ } from './kms_options';
34
39
  import {
35
40
  type ClientEncryptionDataKeyProvider,
36
41
  type CredentialProviders,
@@ -38,11 +43,7 @@ import {
38
43
  type KMSProviders,
39
44
  refreshKMSCredentials
40
45
  } from './providers/index';
41
- import {
42
- type ClientEncryptionSocketOptions,
43
- type CSFLEKMSTlsOptions,
44
- StateMachine
45
- } from './state_machine';
46
+ import { StateMachine } from './state_machine';
46
47
 
47
48
  /**
48
49
  * @public
@@ -75,6 +76,8 @@ export class ClientEncryption {
75
76
  /** @internal */
76
77
  _tlsOptions: CSFLEKMSTlsOptions;
77
78
  /** @internal */
79
+ _kmsConnectCallback?: KMSConnectCallback;
80
+ /** @internal */
78
81
  _kmsProviders: KMSProviders;
79
82
  /** @internal */
80
83
  _timeoutMS?: number;
@@ -125,7 +128,13 @@ export class ClientEncryption {
125
128
  constructor(client: MongoClient, options: ClientEncryptionOptions) {
126
129
  this._client = client;
127
130
  this._proxyOptions = options.proxyOptions ?? {};
131
+ if (this._proxyOptions.proxyHost && options.kmsConnectCallback) {
132
+ throw new MongoCryptInvalidArgumentError(
133
+ 'Cannot set both proxyOptions and kmsConnectCallback'
134
+ );
135
+ }
128
136
  this._tlsOptions = options.tlsOptions ?? {};
137
+ this._kmsConnectCallback = options.kmsConnectCallback;
129
138
  this._kmsProviders = options.kmsProviders || {};
130
139
  const { timeoutMS } = resolveTimeoutOptions(client, options);
131
140
  this._timeoutMS = timeoutMS;
@@ -226,7 +235,8 @@ export class ClientEncryption {
226
235
  const stateMachine = new StateMachine({
227
236
  proxyOptions: this._proxyOptions,
228
237
  tlsOptions: this._tlsOptions,
229
- socketOptions: autoSelectSocketOptions(this._client.s.options)
238
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
239
+ kmsConnectCallback: this._kmsConnectCallback
230
240
  });
231
241
 
232
242
  const timeoutContext =
@@ -295,7 +305,8 @@ export class ClientEncryption {
295
305
  const stateMachine = new StateMachine({
296
306
  proxyOptions: this._proxyOptions,
297
307
  tlsOptions: this._tlsOptions,
298
- socketOptions: autoSelectSocketOptions(this._client.s.options)
308
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
309
+ kmsConnectCallback: this._kmsConnectCallback
299
310
  });
300
311
 
301
312
  const timeoutContext = TimeoutContext.create(
@@ -699,7 +710,8 @@ export class ClientEncryption {
699
710
  const stateMachine = new StateMachine({
700
711
  proxyOptions: this._proxyOptions,
701
712
  tlsOptions: this._tlsOptions,
702
- socketOptions: autoSelectSocketOptions(this._client.s.options)
713
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
714
+ kmsConnectCallback: this._kmsConnectCallback
703
715
  });
704
716
 
705
717
  const timeoutContext =
@@ -797,7 +809,8 @@ export class ClientEncryption {
797
809
  const stateMachine = new StateMachine({
798
810
  proxyOptions: this._proxyOptions,
799
811
  tlsOptions: this._tlsOptions,
800
- socketOptions: autoSelectSocketOptions(this._client.s.options)
812
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
813
+ kmsConnectCallback: this._kmsConnectCallback
801
814
  });
802
815
  const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
803
816
 
@@ -961,6 +974,13 @@ export interface ClientEncryptionOptions {
961
974
  */
962
975
  tlsOptions?: CSFLEKMSTlsOptions;
963
976
 
977
+ /**
978
+ * A callback that establishes the socket used to connect to a KMS host, e.g. to route KMS
979
+ * requests through an HTTP proxy via the HTTP CONNECT method. Mutually exclusive with `proxyOptions`.
980
+ * See {@link KMSConnectCallback} for a usage example.
981
+ */
982
+ kmsConnectCallback?: KMSConnectCallback;
983
+
964
984
  /**
965
985
  * Sets the expiration time for the DEK in the cache in milliseconds. Defaults to 60000. 0 means no timeout.
966
986
  */
@@ -0,0 +1,93 @@
1
+ import { type Duplex } from 'stream';
2
+
3
+ import { type MongoClientOptions } from '../mongo_client';
4
+
5
+ /**
6
+ * @public
7
+ *
8
+ * TLS options to use when connecting. The spec specifically calls out which insecure
9
+ * tls options are not allowed:
10
+ *
11
+ * - tlsAllowInvalidCertificates
12
+ * - tlsAllowInvalidHostnames
13
+ * - tlsInsecure
14
+ *
15
+ * These options are not included in the type, and are ignored if provided.
16
+ */
17
+ export type ClientEncryptionTlsOptions = Pick<
18
+ MongoClientOptions,
19
+ 'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
20
+ >;
21
+
22
+ /** @public */
23
+ export type CSFLEKMSTlsOptions = {
24
+ aws?: ClientEncryptionTlsOptions;
25
+ gcp?: ClientEncryptionTlsOptions;
26
+ kmip?: ClientEncryptionTlsOptions;
27
+ local?: ClientEncryptionTlsOptions;
28
+ azure?: ClientEncryptionTlsOptions;
29
+
30
+ [key: string]: ClientEncryptionTlsOptions | undefined;
31
+ };
32
+
33
+ /**
34
+ * @public
35
+ *
36
+ * Socket options to use for KMS requests.
37
+ */
38
+ export type ClientEncryptionSocketOptions = Pick<
39
+ MongoClientOptions,
40
+ 'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
41
+ >;
42
+
43
+ /**
44
+ * @public
45
+ *
46
+ * A callback that establishes the connection to a KMS host.
47
+ *
48
+ * When provided on `AutoEncryptionOptions` or `ClientEncryptionOptions`, the driver invokes this
49
+ * callback instead of connecting to the KMS host itself, passing the target `host` and `port`. The
50
+ * callback MUST return a `Duplex` stream connected to the KMS host, either directly or tunneled
51
+ * through a proxy; a `net.Socket` satisfies this, as does any other `Duplex`. The returned stream is
52
+ * passed to Node.js' `tls.connect()` as its `socket`, and the driver performs the KMS host's TLS
53
+ * handshake over it using the KMS provider's configured TLS options. The callback therefore MUST NOT
54
+ * perform the KMS host's TLS handshake itself, though it MAY use TLS for its own transport, e.g. when
55
+ * connecting to an HTTPS proxy. This enables routing KMS requests through an HTTP proxy via the HTTP
56
+ * CONNECT method.
57
+ *
58
+ * When the operation has a client-side operation timeout (CSOT) configured, `timeoutMS` is the
59
+ * remaining time budget in milliseconds; it is `undefined` otherwise. The `signal` aborts when the
60
+ * connection attempt exceeds that budget; the callback should stop connecting and reject when it fires.
61
+ *
62
+ * @example <caption>Route KMS requests through an HTTP proxy using the HTTP CONNECT method</caption>
63
+ * ```ts
64
+ * import * as net from 'net';
65
+ *
66
+ * const kmsConnectCallback: KMSConnectCallback = ({ host, port, signal }) =>
67
+ * new Promise((resolve, reject) => {
68
+ * // Open a plain connection to the proxy, not to the KMS host.
69
+ * const socket = net.connect({ host: 'proxy.example.com', port: 8080, signal });
70
+ * socket.once('error', reject);
71
+ * socket.once('connect', () => {
72
+ * // Ask the proxy to tunnel to the KMS host, then hand the socket back for the driver's TLS.
73
+ * socket.write(`CONNECT ${host}:${port} HTTP/1.1\r\nHost: ${host}:${port}\r\n\r\n`);
74
+ * socket.once('data', chunk => {
75
+ * if (chunk.toString('utf8').startsWith('HTTP/1.1 200')) resolve(socket);
76
+ * else reject(new Error('Proxy refused the CONNECT request'));
77
+ * });
78
+ * });
79
+ * });
80
+ *
81
+ * const clientEncryption = new ClientEncryption(keyVaultClient, {
82
+ * keyVaultNamespace,
83
+ * kmsProviders,
84
+ * kmsConnectCallback
85
+ * });
86
+ * ```
87
+ */
88
+ export type KMSConnectCallback = (options: {
89
+ host: string;
90
+ port: number;
91
+ timeoutMS?: number;
92
+ signal: AbortSignal;
93
+ }) => Promise<Duplex>;
@@ -15,7 +15,7 @@ import { type ProxyOptions } from '../cmap/connection';
15
15
  import { CursorTimeoutContext } from '../cursor/abstract_cursor';
16
16
  import { getSocks, type SocksLib } from '../deps';
17
17
  import { MongoOperationTimeoutError } from '../error';
18
- import { type MongoClient, type MongoClientOptions } from '../mongo_client';
18
+ import { type MongoClient } from '../mongo_client';
19
19
  import { type Abortable } from '../mongo_types';
20
20
  import { type CollectionInfo } from '../operations/list_collections';
21
21
  import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
@@ -28,6 +28,12 @@ import {
28
28
  } from '../utils';
29
29
  import { autoSelectSocketOptions, type DataKey } from './client_encryption';
30
30
  import { MongoCryptError } from './errors';
31
+ import {
32
+ type ClientEncryptionSocketOptions,
33
+ type ClientEncryptionTlsOptions,
34
+ type CSFLEKMSTlsOptions,
35
+ type KMSConnectCallback
36
+ } from './kms_options';
31
37
  import { type MongocryptdManager } from './mongocryptd_manager';
32
38
  import { type KMSProviders } from './providers';
33
39
 
@@ -93,44 +99,6 @@ declare module 'mongodb-client-encryption' {
93
99
  }
94
100
  }
95
101
 
96
- /**
97
- * @public
98
- *
99
- * TLS options to use when connecting. The spec specifically calls out which insecure
100
- * tls options are not allowed:
101
- *
102
- * - tlsAllowInvalidCertificates
103
- * - tlsAllowInvalidHostnames
104
- * - tlsInsecure
105
- *
106
- * These options are not included in the type, and are ignored if provided.
107
- */
108
- export type ClientEncryptionTlsOptions = Pick<
109
- MongoClientOptions,
110
- 'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
111
- >;
112
-
113
- /** @public */
114
- export type CSFLEKMSTlsOptions = {
115
- aws?: ClientEncryptionTlsOptions;
116
- gcp?: ClientEncryptionTlsOptions;
117
- kmip?: ClientEncryptionTlsOptions;
118
- local?: ClientEncryptionTlsOptions;
119
- azure?: ClientEncryptionTlsOptions;
120
-
121
- [key: string]: ClientEncryptionTlsOptions | undefined;
122
- };
123
-
124
- /**
125
- * @public
126
- *
127
- * Socket options to use for KMS requests.
128
- */
129
- export type ClientEncryptionSocketOptions = Pick<
130
- MongoClientOptions,
131
- 'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
132
- >;
133
-
134
102
  /**
135
103
  * This is kind of a hack. For `rewrapManyDataKey`, we have tests that
136
104
  * guarantee that when there are no matching keys, `rewrapManyDataKey` returns
@@ -173,6 +141,9 @@ export type StateMachineOptions = {
173
141
 
174
142
  /** Socket specific options we support. */
175
143
  socketOptions: ClientEncryptionSocketOptions;
144
+
145
+ /** Optional callback that establishes the KMS socket, if set. */
146
+ kmsConnectCallback?: KMSConnectCallback;
176
147
  } & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;
177
148
 
178
149
  /**
@@ -388,9 +359,55 @@ export class StateMachine {
388
359
  }
389
360
 
390
361
  let abortListener;
362
+ let kmsRequestTimeout: Timeout | undefined;
391
363
 
392
364
  try {
393
- if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
365
+ options?.signal?.throwIfAborted();
366
+
367
+ if (this.options.kmsConnectCallback) {
368
+ const remainingTimeMS = options?.timeoutContext?.csotEnabled()
369
+ ? options.timeoutContext.getRemainingTimeMSOrThrow(
370
+ `KMS request timed out after ${options.timeoutContext.timeoutMS}ms`
371
+ )
372
+ : undefined;
373
+ // A non-finite budget (timeoutMS: 0) means no timeout; otherwise back the callback with one.
374
+ const timeoutMS = Number.isFinite(remainingTimeMS) ? remainingTimeMS : undefined;
375
+ const controller = new AbortController();
376
+ // Forward the operation's own abort signal so aborting the operation also aborts the callback.
377
+ const forwardAbort = addAbortListener(options?.signal, function () {
378
+ controller.abort(this.reason);
379
+ });
380
+ const connectPromise = this.options.kmsConnectCallback({
381
+ host: socketOptions.host,
382
+ port: socketOptions.port,
383
+ timeoutMS,
384
+ signal: controller.signal
385
+ });
386
+ // If the callback resolves a socket after we already aborted, close it so it does not leak.
387
+ connectPromise.then(
388
+ resolvedSocket => {
389
+ if (controller.signal.aborted) resolvedSocket.destroy();
390
+ },
391
+ () => null
392
+ );
393
+ const timeout = timeoutMS ? Timeout.expires(timeoutMS) : undefined;
394
+ try {
395
+ socketOptions.socket = timeout
396
+ ? await Promise.race([connectPromise, timeout])
397
+ : await connectPromise;
398
+ } catch (err) {
399
+ if (TimeoutError.is(err)) {
400
+ const timeoutError = new MongoOperationTimeoutError('KMS request timed out');
401
+ // Abort with the timeout error as the reason so the callback sees it on `signal.reason`.
402
+ controller.abort(timeoutError);
403
+ throw timeoutError;
404
+ }
405
+ throw onerror(err);
406
+ } finally {
407
+ timeout?.clear();
408
+ forwardAbort?.[kDispose]();
409
+ }
410
+ } else if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
394
411
  netSocket = new net.Socket();
395
412
 
396
413
  const {
@@ -465,20 +482,24 @@ export class StateMachine {
465
482
  resolve();
466
483
  }
467
484
  });
468
- await (options?.timeoutContext?.csotEnabled()
469
- ? Promise.all([
470
- willResolveKmsRequest,
471
- Timeout.expires(options.timeoutContext?.remainingTimeMS)
472
- ])
485
+ const remainingTimeMS = options?.timeoutContext?.csotEnabled()
486
+ ? options.timeoutContext.getRemainingTimeMSOrThrow(
487
+ `KMS request timed out after ${options.timeoutContext.timeoutMS}ms`
488
+ )
489
+ : undefined;
490
+ const timeoutMS = Number.isFinite(remainingTimeMS) ? remainingTimeMS : undefined;
491
+ kmsRequestTimeout = timeoutMS ? Timeout.expires(timeoutMS) : undefined;
492
+ await (kmsRequestTimeout
493
+ ? Promise.race([willResolveKmsRequest, kmsRequestTimeout])
473
494
  : willResolveKmsRequest);
474
495
  } catch (error) {
475
- if (error instanceof TimeoutError)
476
- throw new MongoOperationTimeoutError('KMS request timed out');
496
+ if (TimeoutError.is(error)) throw new MongoOperationTimeoutError('KMS request timed out');
477
497
  throw error;
478
498
  } finally {
479
499
  // There's no need for any more activity on this socket at this point.
480
500
  destroySockets();
481
501
  abortListener?.[kDispose]();
502
+ kmsRequestTimeout?.clear();
482
503
  }
483
504
  }
484
505
 
@@ -69,10 +69,7 @@ export class GSSAPI extends AuthProvider {
69
69
  }
70
70
 
71
71
  async function makeKerberosClient({
72
- options: {
73
- hostAddress,
74
- runtime: { os }
75
- },
72
+ options: { hostAddress, runtime },
76
73
  credentials
77
74
  }: AuthContext): Promise<KerberosClient> {
78
75
  if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) {
@@ -81,6 +78,8 @@ async function makeKerberosClient({
81
78
  );
82
79
  }
83
80
 
81
+ const { os } = await runtime;
82
+
84
83
  loadKrb();
85
84
  if ('kModuleError' in krb) {
86
85
  throw krb['kModuleError'];
@@ -25,7 +25,6 @@ function getDefaultAuthMechanism(hello: Document | null): AuthMechanism {
25
25
  }
26
26
  }
27
27
 
28
- // Default auth mechanism for 4.0 and higher.
29
28
  return AuthMechanism.MONGODB_SCRAM_SHA256;
30
29
  }
31
30
 
@@ -1,11 +1,7 @@
1
1
  import { type Binary, type BSONSerializeOptions, ByteUtils } from '../../bson';
2
2
  import * as BSON from '../../bson';
3
- import {
4
- MongoCompatibilityError,
5
- MongoMissingCredentialsError,
6
- MongoRuntimeError
7
- } from '../../error';
8
- import { maxWireVersion, ns, randomBytes } from '../../utils';
3
+ import { MongoMissingCredentialsError, MongoRuntimeError } from '../../error';
4
+ import { ns, randomBytes } from '../../utils';
9
5
  import { type AuthContext, AuthProvider } from './auth_provider';
10
6
  import {
11
7
  type AWSCredentialProvider,
@@ -45,12 +41,6 @@ export class MongoDBAWS extends AuthProvider {
45
41
  throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
46
42
  }
47
43
 
48
- if (maxWireVersion(connection) < 9) {
49
- throw new MongoCompatibilityError(
50
- 'MONGODB-AWS authentication requires MongoDB version 4.4 or later'
51
- );
52
- }
53
-
54
44
  authContext.credentials = await makeTempCredentials(
55
45
  authContext.credentials,
56
46
  this.credentialFetcher
@@ -32,7 +32,7 @@ export class CommandStartedEvent {
32
32
  /**
33
33
  * Server generated connection id
34
34
  * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId"
35
- * from the server on 4.2+.
35
+ * from the server.
36
36
  */
37
37
  serverConnectionId: bigint | null;
38
38
  serviceId?: ObjectId;
@@ -88,7 +88,7 @@ export class CommandSucceededEvent {
88
88
  connectionId?: string | number;
89
89
  /**
90
90
  * Server generated connection id
91
- * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server on 4.2+.
91
+ * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server.
92
92
  */
93
93
  serverConnectionId: bigint | null;
94
94
  requestId: number;
@@ -148,7 +148,7 @@ export class CommandFailedEvent {
148
148
  connectionId?: string | number;
149
149
  /**
150
150
  * Server generated connection id
151
- * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server on 4.2+.
151
+ * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server.
152
152
  */
153
153
  serverConnectionId: bigint | null;
154
154
  requestId: number;
@@ -249,13 +249,12 @@ const LEGACY_FIND_OPTIONS_MAP = {
249
249
  function extractCommand(command: WriteProtocolMessageType): Document {
250
250
  if (command instanceof OpMsgRequest) {
251
251
  const cmd = { ...command.command };
252
- // For OP_MSG with payload type 1 we need to pull the documents
253
- // array out of the document sequence for monitoring.
254
- if (cmd.ops instanceof DocumentSequence) {
255
- cmd.ops = cmd.ops.documents;
256
- }
257
- if (cmd.nsInfo instanceof DocumentSequence) {
258
- cmd.nsInfo = cmd.nsInfo.documents;
252
+ // For OP_MSG payload type 1, replace any document-sequence field with its
253
+ // documents array for monitoring (ops, nsInfo, documents, updates, deletes).
254
+ for (const key of Object.keys(cmd)) {
255
+ if (cmd[key] instanceof DocumentSequence) {
256
+ cmd[key] = cmd[key].documents;
257
+ }
259
258
  }
260
259
  return cmd;
261
260
  }
@@ -500,6 +500,23 @@ export class DocumentSequence {
500
500
  }
501
501
  }
502
502
 
503
+ /**
504
+ * Build a DocumentSequence from documents that have already been serialized,
505
+ * reusing the provided buffers instead of re-serializing.
506
+ * @internal
507
+ */
508
+ export function makeDocumentSequence(
509
+ field: string,
510
+ documents: Document[],
511
+ serialized: Uint8Array[]
512
+ ): DocumentSequence {
513
+ const sequence = new DocumentSequence(field);
514
+ for (let i = 0; i < documents.length; i++) {
515
+ sequence.push(documents[i], serialized[i]);
516
+ }
517
+ return sequence;
518
+ }
519
+
503
520
  /** @internal */
504
521
  export class OpMsgRequest {
505
522
  requestId: number;
@@ -174,7 +174,7 @@ export async function performInitialHandshake(
174
174
 
175
175
  if (error instanceof MongoError) {
176
176
  error.addErrorLabel(MongoErrorLabel.HandshakeError);
177
- if (needsRetryableWriteLabel(error, response.maxWireVersion, conn.description.type)) {
177
+ if (needsRetryableWriteLabel(error)) {
178
178
  error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
179
179
  }
180
180
  }
@@ -224,7 +224,7 @@ export interface HandshakeDocument extends Document {
224
224
  compression: string[];
225
225
  saslSupportedMechs?: string;
226
226
  loadBalanced?: boolean;
227
- backpressure: true;
227
+ backpressure: '2';
228
228
  }
229
229
 
230
230
  /**
@@ -242,7 +242,7 @@ export async function prepareHandshakeDocument(
242
242
 
243
243
  const handshakeDoc: HandshakeDocument = {
244
244
  [serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
245
- backpressure: true,
245
+ backpressure: '2',
246
246
  helloOk: true,
247
247
  client: clientMetadata,
248
248
  compression: compressors