mongodb 6.10.0 → 6.11.0-dev.20241123.sha.32f7ac63

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 (228) hide show
  1. package/README.md +2 -0
  2. package/lib/admin.js +3 -2
  3. package/lib/admin.js.map +1 -1
  4. package/lib/beta.d.ts +562 -45
  5. package/lib/bulk/common.js +4 -4
  6. package/lib/bulk/common.js.map +1 -1
  7. package/lib/change_stream.js +111 -51
  8. package/lib/change_stream.js.map +1 -1
  9. package/lib/client-side-encryption/auto_encrypter.js +8 -5
  10. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  11. package/lib/client-side-encryption/client_encryption.js +48 -18
  12. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  13. package/lib/client-side-encryption/state_machine.js +43 -29
  14. package/lib/client-side-encryption/state_machine.js.map +1 -1
  15. package/lib/cmap/auth/mongo_credentials.js +5 -2
  16. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_aws.js +1 -1
  18. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  19. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +38 -0
  20. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -0
  21. package/lib/cmap/auth/mongodb_oidc.js +2 -0
  22. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  23. package/lib/cmap/connect.js +13 -1
  24. package/lib/cmap/connect.js.map +1 -1
  25. package/lib/cmap/connection.js +75 -17
  26. package/lib/cmap/connection.js.map +1 -1
  27. package/lib/cmap/connection_pool.js +14 -12
  28. package/lib/cmap/connection_pool.js.map +1 -1
  29. package/lib/cmap/wire_protocol/on_data.js +5 -1
  30. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  31. package/lib/cmap/wire_protocol/responses.js +30 -0
  32. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  33. package/lib/collection.js +62 -3
  34. package/lib/collection.js.map +1 -1
  35. package/lib/connection_string.js +2 -0
  36. package/lib/connection_string.js.map +1 -1
  37. package/lib/cursor/abstract_cursor.js +221 -38
  38. package/lib/cursor/abstract_cursor.js.map +1 -1
  39. package/lib/cursor/aggregation_cursor.js +29 -7
  40. package/lib/cursor/aggregation_cursor.js.map +1 -1
  41. package/lib/cursor/change_stream_cursor.js +2 -2
  42. package/lib/cursor/change_stream_cursor.js.map +1 -1
  43. package/lib/cursor/client_bulk_write_cursor.js +1 -1
  44. package/lib/cursor/client_bulk_write_cursor.js.map +1 -1
  45. package/lib/cursor/find_cursor.js +18 -8
  46. package/lib/cursor/find_cursor.js.map +1 -1
  47. package/lib/cursor/list_collections_cursor.js +1 -1
  48. package/lib/cursor/list_collections_cursor.js.map +1 -1
  49. package/lib/cursor/list_indexes_cursor.js +1 -1
  50. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  51. package/lib/cursor/run_command_cursor.js +6 -4
  52. package/lib/cursor/run_command_cursor.js.map +1 -1
  53. package/lib/db.js +63 -3
  54. package/lib/db.js.map +1 -1
  55. package/lib/error.js +34 -9
  56. package/lib/error.js.map +1 -1
  57. package/lib/explain.js +57 -1
  58. package/lib/explain.js.map +1 -1
  59. package/lib/gridfs/download.js +31 -3
  60. package/lib/gridfs/download.js.map +1 -1
  61. package/lib/gridfs/index.js +49 -14
  62. package/lib/gridfs/index.js.map +1 -1
  63. package/lib/gridfs/upload.js +80 -22
  64. package/lib/gridfs/upload.js.map +1 -1
  65. package/lib/index.js +9 -5
  66. package/lib/index.js.map +1 -1
  67. package/lib/mongo_client.js +70 -1
  68. package/lib/mongo_client.js.map +1 -1
  69. package/lib/operations/aggregate.js +2 -2
  70. package/lib/operations/aggregate.js.map +1 -1
  71. package/lib/operations/bulk_write.js +7 -2
  72. package/lib/operations/bulk_write.js.map +1 -1
  73. package/lib/operations/client_bulk_write/client_bulk_write.js +3 -3
  74. package/lib/operations/client_bulk_write/client_bulk_write.js.map +1 -1
  75. package/lib/operations/client_bulk_write/executor.js +14 -3
  76. package/lib/operations/client_bulk_write/executor.js.map +1 -1
  77. package/lib/operations/command.js +5 -2
  78. package/lib/operations/command.js.map +1 -1
  79. package/lib/operations/count.js +2 -2
  80. package/lib/operations/count.js.map +1 -1
  81. package/lib/operations/create_collection.js +8 -7
  82. package/lib/operations/create_collection.js.map +1 -1
  83. package/lib/operations/delete.js +6 -6
  84. package/lib/operations/delete.js.map +1 -1
  85. package/lib/operations/distinct.js +2 -2
  86. package/lib/operations/distinct.js.map +1 -1
  87. package/lib/operations/drop.js +8 -8
  88. package/lib/operations/drop.js.map +1 -1
  89. package/lib/operations/estimated_document_count.js +2 -2
  90. package/lib/operations/estimated_document_count.js.map +1 -1
  91. package/lib/operations/execute_operation.js +16 -10
  92. package/lib/operations/execute_operation.js.map +1 -1
  93. package/lib/operations/find.js +6 -3
  94. package/lib/operations/find.js.map +1 -1
  95. package/lib/operations/find_and_modify.js +2 -2
  96. package/lib/operations/find_and_modify.js.map +1 -1
  97. package/lib/operations/get_more.js +2 -1
  98. package/lib/operations/get_more.js.map +1 -1
  99. package/lib/operations/indexes.js +6 -6
  100. package/lib/operations/indexes.js.map +1 -1
  101. package/lib/operations/insert.js +6 -6
  102. package/lib/operations/insert.js.map +1 -1
  103. package/lib/operations/kill_cursors.js +5 -2
  104. package/lib/operations/kill_cursors.js.map +1 -1
  105. package/lib/operations/list_collections.js +2 -2
  106. package/lib/operations/list_collections.js.map +1 -1
  107. package/lib/operations/list_databases.js +2 -2
  108. package/lib/operations/list_databases.js.map +1 -1
  109. package/lib/operations/operation.js.map +1 -1
  110. package/lib/operations/profiling_level.js +2 -2
  111. package/lib/operations/profiling_level.js.map +1 -1
  112. package/lib/operations/remove_user.js +2 -2
  113. package/lib/operations/remove_user.js.map +1 -1
  114. package/lib/operations/rename.js +2 -2
  115. package/lib/operations/rename.js.map +1 -1
  116. package/lib/operations/run_command.js +6 -4
  117. package/lib/operations/run_command.js.map +1 -1
  118. package/lib/operations/search_indexes/create.js +5 -2
  119. package/lib/operations/search_indexes/create.js.map +1 -1
  120. package/lib/operations/search_indexes/drop.js +2 -2
  121. package/lib/operations/search_indexes/drop.js.map +1 -1
  122. package/lib/operations/search_indexes/update.js +2 -2
  123. package/lib/operations/search_indexes/update.js.map +1 -1
  124. package/lib/operations/set_profiling_level.js +2 -2
  125. package/lib/operations/set_profiling_level.js.map +1 -1
  126. package/lib/operations/stats.js +2 -2
  127. package/lib/operations/stats.js.map +1 -1
  128. package/lib/operations/update.js +8 -8
  129. package/lib/operations/update.js.map +1 -1
  130. package/lib/operations/validate_collection.js +2 -2
  131. package/lib/operations/validate_collection.js.map +1 -1
  132. package/lib/read_concern.js +1 -1
  133. package/lib/sdam/common.js +0 -7
  134. package/lib/sdam/common.js.map +1 -1
  135. package/lib/sdam/server.js +4 -1
  136. package/lib/sdam/server.js.map +1 -1
  137. package/lib/sdam/server_description.js +4 -2
  138. package/lib/sdam/server_description.js.map +1 -1
  139. package/lib/sdam/server_selection.js +5 -2
  140. package/lib/sdam/server_selection.js.map +1 -1
  141. package/lib/sdam/topology.js +38 -15
  142. package/lib/sdam/topology.js.map +1 -1
  143. package/lib/sessions.js +157 -98
  144. package/lib/sessions.js.map +1 -1
  145. package/lib/timeout.js +231 -16
  146. package/lib/timeout.js.map +1 -1
  147. package/lib/utils.js +36 -19
  148. package/lib/utils.js.map +1 -1
  149. package/lib/write_concern.js.map +1 -1
  150. package/mongodb.d.ts +562 -45
  151. package/package.json +18 -17
  152. package/src/admin.ts +6 -2
  153. package/src/bulk/common.ts +17 -5
  154. package/src/change_stream.ts +127 -52
  155. package/src/client-side-encryption/auto_encrypter.ts +12 -5
  156. package/src/client-side-encryption/client_encryption.ts +103 -20
  157. package/src/client-side-encryption/state_machine.ts +66 -32
  158. package/src/cmap/auth/mongo_credentials.ts +6 -3
  159. package/src/cmap/auth/mongodb_aws.ts +1 -1
  160. package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +38 -0
  161. package/src/cmap/auth/mongodb_oidc.ts +3 -1
  162. package/src/cmap/connect.ts +18 -1
  163. package/src/cmap/connection.ts +105 -17
  164. package/src/cmap/connection_pool.ts +15 -17
  165. package/src/cmap/handshake/client_metadata.ts +1 -1
  166. package/src/cmap/wire_protocol/on_data.ts +11 -1
  167. package/src/cmap/wire_protocol/responses.ts +35 -1
  168. package/src/collection.ts +81 -9
  169. package/src/connection_string.ts +2 -0
  170. package/src/cursor/abstract_cursor.ts +287 -39
  171. package/src/cursor/aggregation_cursor.ts +54 -8
  172. package/src/cursor/change_stream_cursor.ts +6 -2
  173. package/src/cursor/client_bulk_write_cursor.ts +6 -2
  174. package/src/cursor/find_cursor.ts +40 -9
  175. package/src/cursor/list_collections_cursor.ts +1 -1
  176. package/src/cursor/list_indexes_cursor.ts +1 -1
  177. package/src/cursor/run_command_cursor.ts +50 -5
  178. package/src/db.ts +75 -7
  179. package/src/error.ts +33 -8
  180. package/src/explain.ts +85 -0
  181. package/src/gridfs/download.ts +43 -4
  182. package/src/gridfs/index.ts +64 -16
  183. package/src/gridfs/upload.ts +153 -45
  184. package/src/index.ts +27 -5
  185. package/src/mongo_client.ts +76 -4
  186. package/src/operations/aggregate.ts +10 -2
  187. package/src/operations/bulk_write.ts +9 -2
  188. package/src/operations/client_bulk_write/client_bulk_write.ts +11 -3
  189. package/src/operations/client_bulk_write/executor.ts +15 -3
  190. package/src/operations/command.ts +18 -8
  191. package/src/operations/count.ts +10 -3
  192. package/src/operations/create_collection.ts +14 -7
  193. package/src/operations/delete.ts +15 -6
  194. package/src/operations/distinct.ts +7 -2
  195. package/src/operations/drop.ts +18 -8
  196. package/src/operations/estimated_document_count.ts +7 -2
  197. package/src/operations/execute_operation.ts +22 -13
  198. package/src/operations/find.ts +17 -5
  199. package/src/operations/find_and_modify.ts +7 -2
  200. package/src/operations/get_more.ts +4 -1
  201. package/src/operations/indexes.ts +20 -7
  202. package/src/operations/insert.ts +13 -6
  203. package/src/operations/kill_cursors.ts +10 -2
  204. package/src/operations/list_collections.ts +10 -1
  205. package/src/operations/list_databases.ts +9 -2
  206. package/src/operations/operation.ts +16 -2
  207. package/src/operations/profiling_level.ts +7 -2
  208. package/src/operations/remove_user.ts +7 -2
  209. package/src/operations/rename.ts +7 -2
  210. package/src/operations/run_command.ts +23 -4
  211. package/src/operations/search_indexes/create.ts +10 -2
  212. package/src/operations/search_indexes/drop.ts +7 -2
  213. package/src/operations/search_indexes/update.ts +7 -2
  214. package/src/operations/set_profiling_level.ts +4 -2
  215. package/src/operations/stats.ts +7 -2
  216. package/src/operations/update.ts +16 -8
  217. package/src/operations/validate_collection.ts +7 -2
  218. package/src/read_concern.ts +1 -1
  219. package/src/sdam/common.ts +0 -11
  220. package/src/sdam/server.ts +14 -4
  221. package/src/sdam/server_description.ts +6 -2
  222. package/src/sdam/server_selection.ts +5 -2
  223. package/src/sdam/topology.ts +43 -27
  224. package/src/sessions.ts +206 -120
  225. package/src/timeout.ts +327 -23
  226. package/src/transactions.ts +1 -1
  227. package/src/utils.ts +47 -30
  228. package/src/write_concern.ts +6 -3
@@ -24,7 +24,8 @@ import { type MongoClient, type MongoClientOptions } from '../mongo_client';
24
24
  import { type Filter, type WithId } from '../mongo_types';
25
25
  import { type CreateCollectionOptions } from '../operations/create_collection';
26
26
  import { type DeleteResult } from '../operations/delete';
27
- import { MongoDBCollectionNamespace } from '../utils';
27
+ import { type CSOTTimeoutContext, TimeoutContext } from '../timeout';
28
+ import { MongoDBCollectionNamespace, resolveTimeoutOptions } from '../utils';
28
29
  import * as cryptoCallbacks from './crypto_callbacks';
29
30
  import {
30
31
  MongoCryptCreateDataKeyError,
@@ -74,6 +75,8 @@ export class ClientEncryption {
74
75
  _tlsOptions: CSFLEKMSTlsOptions;
75
76
  /** @internal */
76
77
  _kmsProviders: KMSProviders;
78
+ /** @internal */
79
+ _timeoutMS?: number;
77
80
 
78
81
  /** @internal */
79
82
  _mongoCrypt: MongoCrypt;
@@ -120,6 +123,8 @@ export class ClientEncryption {
120
123
  this._proxyOptions = options.proxyOptions ?? {};
121
124
  this._tlsOptions = options.tlsOptions ?? {};
122
125
  this._kmsProviders = options.kmsProviders || {};
126
+ const { timeoutMS } = resolveTimeoutOptions(client, options);
127
+ this._timeoutMS = timeoutMS;
123
128
 
124
129
  if (options.keyVaultNamespace == null) {
125
130
  throw new MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`');
@@ -212,10 +217,16 @@ export class ClientEncryption {
212
217
  const stateMachine = new StateMachine({
213
218
  proxyOptions: this._proxyOptions,
214
219
  tlsOptions: this._tlsOptions,
215
- socketOptions: autoSelectSocketOptions(this._client.options)
220
+ socketOptions: autoSelectSocketOptions(this._client.s.options)
216
221
  });
217
222
 
218
- const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;
223
+ const timeoutContext =
224
+ options?.timeoutContext ??
225
+ TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }));
226
+
227
+ const dataKey = deserialize(
228
+ await stateMachine.execute(this, context, timeoutContext)
229
+ ) as DataKey;
219
230
 
220
231
  const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
221
232
  this._keyVaultNamespace
@@ -224,7 +235,12 @@ export class ClientEncryption {
224
235
  const { insertedId } = await this._keyVaultClient
225
236
  .db(dbName)
226
237
  .collection<DataKey>(collectionName)
227
- .insertOne(dataKey, { writeConcern: { w: 'majority' } });
238
+ .insertOne(dataKey, {
239
+ writeConcern: { w: 'majority' },
240
+ timeoutMS: timeoutContext?.csotEnabled()
241
+ ? timeoutContext?.getRemainingTimeMSOrThrow()
242
+ : undefined
243
+ });
228
244
 
229
245
  return insertedId;
230
246
  }
@@ -270,10 +286,14 @@ export class ClientEncryption {
270
286
  const stateMachine = new StateMachine({
271
287
  proxyOptions: this._proxyOptions,
272
288
  tlsOptions: this._tlsOptions,
273
- socketOptions: autoSelectSocketOptions(this._client.options)
289
+ socketOptions: autoSelectSocketOptions(this._client.s.options)
274
290
  });
275
291
 
276
- const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
292
+ const timeoutContext = TimeoutContext.create(
293
+ resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS })
294
+ );
295
+
296
+ const { v: dataKeys } = deserialize(await stateMachine.execute(this, context, timeoutContext));
277
297
  if (dataKeys.length === 0) {
278
298
  return {};
279
299
  }
@@ -303,7 +323,8 @@ export class ClientEncryption {
303
323
  .db(dbName)
304
324
  .collection<DataKey>(collectionName)
305
325
  .bulkWrite(replacements, {
306
- writeConcern: { w: 'majority' }
326
+ writeConcern: { w: 'majority' },
327
+ timeoutMS: timeoutContext.csotEnabled() ? timeoutContext?.remainingTimeMS : undefined
307
328
  });
308
329
 
309
330
  return { bulkWriteResult: result };
@@ -332,7 +353,7 @@ export class ClientEncryption {
332
353
  return await this._keyVaultClient
333
354
  .db(dbName)
334
355
  .collection<DataKey>(collectionName)
335
- .deleteOne({ _id }, { writeConcern: { w: 'majority' } });
356
+ .deleteOne({ _id }, { writeConcern: { w: 'majority' }, timeoutMS: this._timeoutMS });
336
357
  }
337
358
 
338
359
  /**
@@ -355,7 +376,7 @@ export class ClientEncryption {
355
376
  return this._keyVaultClient
356
377
  .db(dbName)
357
378
  .collection<DataKey>(collectionName)
358
- .find({}, { readConcern: { level: 'majority' } });
379
+ .find({}, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });
359
380
  }
360
381
 
361
382
  /**
@@ -381,7 +402,7 @@ export class ClientEncryption {
381
402
  return await this._keyVaultClient
382
403
  .db(dbName)
383
404
  .collection<DataKey>(collectionName)
384
- .findOne({ _id }, { readConcern: { level: 'majority' } });
405
+ .findOne({ _id }, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });
385
406
  }
386
407
 
387
408
  /**
@@ -408,7 +429,10 @@ export class ClientEncryption {
408
429
  return await this._keyVaultClient
409
430
  .db(dbName)
410
431
  .collection<DataKey>(collectionName)
411
- .findOne({ keyAltNames: keyAltName }, { readConcern: { level: 'majority' } });
432
+ .findOne(
433
+ { keyAltNames: keyAltName },
434
+ { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS }
435
+ );
412
436
  }
413
437
 
414
438
  /**
@@ -442,7 +466,7 @@ export class ClientEncryption {
442
466
  .findOneAndUpdate(
443
467
  { _id },
444
468
  { $addToSet: { keyAltNames: keyAltName } },
445
- { writeConcern: { w: 'majority' }, returnDocument: 'before' }
469
+ { writeConcern: { w: 'majority' }, returnDocument: 'before', timeoutMS: this._timeoutMS }
446
470
  );
447
471
 
448
472
  return value;
@@ -498,12 +522,14 @@ export class ClientEncryption {
498
522
  }
499
523
  }
500
524
  ];
525
+
501
526
  const value = await this._keyVaultClient
502
527
  .db(dbName)
503
528
  .collection<DataKey>(collectionName)
504
529
  .findOneAndUpdate({ _id }, pipeline, {
505
530
  writeConcern: { w: 'majority' },
506
- returnDocument: 'before'
531
+ returnDocument: 'before',
532
+ timeoutMS: this._timeoutMS
507
533
  });
508
534
 
509
535
  return value;
@@ -541,16 +567,25 @@ export class ClientEncryption {
541
567
  }
542
568
  } = options;
543
569
 
570
+ const timeoutContext =
571
+ this._timeoutMS != null
572
+ ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))
573
+ : undefined;
574
+
544
575
  if (Array.isArray(encryptedFields.fields)) {
545
576
  const createDataKeyPromises = encryptedFields.fields.map(async field =>
546
577
  field == null || typeof field !== 'object' || field.keyId != null
547
578
  ? field
548
579
  : {
549
580
  ...field,
550
- keyId: await this.createDataKey(provider, { masterKey })
581
+ keyId: await this.createDataKey(provider, {
582
+ masterKey,
583
+ // clone the timeoutContext
584
+ // in order to avoid sharing the same timeout for server selection and connection checkout across different concurrent operations
585
+ timeoutContext: timeoutContext?.csotEnabled() ? timeoutContext?.clone() : undefined
586
+ })
551
587
  }
552
588
  );
553
-
554
589
  const createDataKeyResolutions = await Promise.allSettled(createDataKeyPromises);
555
590
 
556
591
  encryptedFields.fields = createDataKeyResolutions.map((resolution, index) =>
@@ -568,7 +603,10 @@ export class ClientEncryption {
568
603
  try {
569
604
  const collection = await db.createCollection<TSchema>(name, {
570
605
  ...createCollectionOptions,
571
- encryptedFields
606
+ encryptedFields,
607
+ timeoutMS: timeoutContext?.csotEnabled()
608
+ ? timeoutContext?.getRemainingTimeMSOrThrow()
609
+ : undefined
572
610
  });
573
611
  return { collection, encryptedFields };
574
612
  } catch (cause) {
@@ -650,10 +688,15 @@ export class ClientEncryption {
650
688
  const stateMachine = new StateMachine({
651
689
  proxyOptions: this._proxyOptions,
652
690
  tlsOptions: this._tlsOptions,
653
- socketOptions: autoSelectSocketOptions(this._client.options)
691
+ socketOptions: autoSelectSocketOptions(this._client.s.options)
654
692
  });
655
693
 
656
- const { v } = deserialize(await stateMachine.execute(this, context));
694
+ const timeoutContext =
695
+ this._timeoutMS != null
696
+ ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))
697
+ : undefined;
698
+
699
+ const { v } = deserialize(await stateMachine.execute(this, context, timeoutContext));
657
700
 
658
701
  return v;
659
702
  }
@@ -729,11 +772,15 @@ export class ClientEncryption {
729
772
  const stateMachine = new StateMachine({
730
773
  proxyOptions: this._proxyOptions,
731
774
  tlsOptions: this._tlsOptions,
732
- socketOptions: autoSelectSocketOptions(this._client.options)
775
+ socketOptions: autoSelectSocketOptions(this._client.s.options)
733
776
  });
734
777
  const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
735
778
 
736
- const { v } = deserialize(await stateMachine.execute(this, context));
779
+ const timeoutContext =
780
+ this._timeoutMS != null
781
+ ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))
782
+ : undefined;
783
+ const { v } = deserialize(await stateMachine.execute(this, context, timeoutContext));
737
784
  return v;
738
785
  }
739
786
  }
@@ -818,6 +865,39 @@ export interface ClientEncryptionOptions {
818
865
  * TLS options for kms providers to use.
819
866
  */
820
867
  tlsOptions?: CSFLEKMSTlsOptions;
868
+
869
+ /**
870
+ * @experimental
871
+ *
872
+ * The timeout setting to be used for all the operations on ClientEncryption.
873
+ *
874
+ * When provided, `timeoutMS` is used as the timeout for each operation executed on
875
+ * the ClientEncryption object. For example:
876
+ *
877
+ * ```typescript
878
+ * const clientEncryption = new ClientEncryption(client, {
879
+ * timeoutMS: 1_000
880
+ * kmsProviders: { local: { key: '<KEY>' } }
881
+ * });
882
+ *
883
+ * // `1_000` is used as the timeout for createDataKey call
884
+ * await clientEncryption.createDataKey('local');
885
+ * ```
886
+ *
887
+ * If `timeoutMS` is configured on the provided client, the client's `timeoutMS` value
888
+ * will be used unless `timeoutMS` is also provided as a client encryption option.
889
+ *
890
+ * ```typescript
891
+ * const client = new MongoClient('<uri>', { timeoutMS: 2_000 });
892
+ *
893
+ * // timeoutMS is set to 1_000 on clientEncryption
894
+ * const clientEncryption = new ClientEncryption(client, {
895
+ * timeoutMS: 1_000
896
+ * kmsProviders: { local: { key: '<KEY>' } }
897
+ * });
898
+ * ```
899
+ */
900
+ timeoutMS?: number;
821
901
  }
822
902
 
823
903
  /**
@@ -946,6 +1026,9 @@ export interface ClientEncryptionCreateDataKeyProviderOptions {
946
1026
 
947
1027
  /** @experimental */
948
1028
  keyMaterial?: Buffer | Binary;
1029
+
1030
+ /** @internal */
1031
+ timeoutContext?: CSOTTimeoutContext;
949
1032
  }
950
1033
 
951
1034
  /**
@@ -11,8 +11,11 @@ import {
11
11
  serialize
12
12
  } from '../bson';
13
13
  import { type ProxyOptions } from '../cmap/connection';
14
+ import { CursorTimeoutContext } from '../cursor/abstract_cursor';
14
15
  import { getSocks, type SocksLib } from '../deps';
16
+ import { MongoOperationTimeoutError } from '../error';
15
17
  import { type MongoClient, type MongoClientOptions } from '../mongo_client';
18
+ import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
16
19
  import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
17
20
  import { autoSelectSocketOptions, type DataKey } from './client_encryption';
18
21
  import { MongoCryptError } from './errors';
@@ -173,6 +176,7 @@ export type StateMachineOptions = {
173
176
  * An internal class that executes across a MongoCryptContext until either
174
177
  * a finishing state or an error is reached. Do not instantiate directly.
175
178
  */
179
+ // TODO(DRIVERS-2671): clarify CSOT behavior for FLE APIs
176
180
  export class StateMachine {
177
181
  constructor(
178
182
  private options: StateMachineOptions,
@@ -182,7 +186,11 @@ export class StateMachine {
182
186
  /**
183
187
  * Executes the state machine according to the specification
184
188
  */
185
- async execute(executor: StateMachineExecutable, context: MongoCryptContext): Promise<Uint8Array> {
189
+ async execute(
190
+ executor: StateMachineExecutable,
191
+ context: MongoCryptContext,
192
+ timeoutContext?: TimeoutContext
193
+ ): Promise<Uint8Array> {
186
194
  const keyVaultNamespace = executor._keyVaultNamespace;
187
195
  const keyVaultClient = executor._keyVaultClient;
188
196
  const metaDataClient = executor._metaDataClient;
@@ -201,8 +209,13 @@ export class StateMachine {
201
209
  'unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_COLLINFO but metadata client is undefined'
202
210
  );
203
211
  }
204
- const collInfo = await this.fetchCollectionInfo(metaDataClient, context.ns, filter);
205
212
 
213
+ const collInfo = await this.fetchCollectionInfo(
214
+ metaDataClient,
215
+ context.ns,
216
+ filter,
217
+ timeoutContext
218
+ );
206
219
  if (collInfo) {
207
220
  context.addMongoOperationResponse(collInfo);
208
221
  }
@@ -222,9 +235,9 @@ export class StateMachine {
222
235
  // When we are using the shared library, we don't have a mongocryptd manager.
223
236
  const markedCommand: Uint8Array = mongocryptdManager
224
237
  ? await mongocryptdManager.withRespawn(
225
- this.markCommand.bind(this, mongocryptdClient, context.ns, command)
238
+ this.markCommand.bind(this, mongocryptdClient, context.ns, command, timeoutContext)
226
239
  )
227
- : await this.markCommand(mongocryptdClient, context.ns, command);
240
+ : await this.markCommand(mongocryptdClient, context.ns, command, timeoutContext);
228
241
 
229
242
  context.addMongoOperationResponse(markedCommand);
230
243
  context.finishMongoOperation();
@@ -233,7 +246,12 @@ export class StateMachine {
233
246
 
234
247
  case MONGOCRYPT_CTX_NEED_MONGO_KEYS: {
235
248
  const filter = context.nextMongoOperation();
236
- const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter);
249
+ const keys = await this.fetchKeys(
250
+ keyVaultClient,
251
+ keyVaultNamespace,
252
+ filter,
253
+ timeoutContext
254
+ );
237
255
 
238
256
  if (keys.length === 0) {
239
257
  // See docs on EMPTY_V
@@ -255,9 +273,7 @@ export class StateMachine {
255
273
  }
256
274
 
257
275
  case MONGOCRYPT_CTX_NEED_KMS: {
258
- const requests = Array.from(this.requests(context));
259
- await Promise.all(requests);
260
-
276
+ await Promise.all(this.requests(context, timeoutContext));
261
277
  context.finishKMSRequests();
262
278
  break;
263
279
  }
@@ -299,7 +315,7 @@ export class StateMachine {
299
315
  * @param kmsContext - A C++ KMS context returned from the bindings
300
316
  * @returns A promise that resolves when the KMS reply has be fully parsed
301
317
  */
302
- async kmsRequest(request: MongoCryptKMSRequest): Promise<void> {
318
+ async kmsRequest(request: MongoCryptKMSRequest, timeoutContext?: TimeoutContext): Promise<void> {
303
319
  const parsedUrl = request.endpoint.split(':');
304
320
  const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
305
321
  const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
@@ -329,10 +345,6 @@ export class StateMachine {
329
345
  }
330
346
  }
331
347
 
332
- function ontimeout() {
333
- return new MongoCryptError('KMS request timed out');
334
- }
335
-
336
348
  function onerror(cause: Error) {
337
349
  return new MongoCryptError('KMS request failed', { cause });
338
350
  }
@@ -364,7 +376,6 @@ export class StateMachine {
364
376
  resolve: resolveOnNetSocketConnect
365
377
  } = promiseWithResolvers<void>();
366
378
  netSocket
367
- .once('timeout', () => rejectOnNetSocketError(ontimeout()))
368
379
  .once('error', err => rejectOnNetSocketError(onerror(err)))
369
380
  .once('close', () => rejectOnNetSocketError(onclose()))
370
381
  .once('connect', () => resolveOnNetSocketConnect());
@@ -410,8 +421,8 @@ export class StateMachine {
410
421
  reject: rejectOnTlsSocketError,
411
422
  resolve
412
423
  } = promiseWithResolvers<void>();
424
+
413
425
  socket
414
- .once('timeout', () => rejectOnTlsSocketError(ontimeout()))
415
426
  .once('error', err => rejectOnTlsSocketError(onerror(err)))
416
427
  .once('close', () => rejectOnTlsSocketError(onclose()))
417
428
  .on('data', data => {
@@ -425,20 +436,26 @@ export class StateMachine {
425
436
  resolve();
426
437
  }
427
438
  });
428
- await willResolveKmsRequest;
439
+ await (timeoutContext?.csotEnabled()
440
+ ? Promise.all([willResolveKmsRequest, Timeout.expires(timeoutContext?.remainingTimeMS)])
441
+ : willResolveKmsRequest);
442
+ } catch (error) {
443
+ if (error instanceof TimeoutError)
444
+ throw new MongoOperationTimeoutError('KMS request timed out');
445
+ throw error;
429
446
  } finally {
430
447
  // There's no need for any more activity on this socket at this point.
431
448
  destroySockets();
432
449
  }
433
450
  }
434
451
 
435
- *requests(context: MongoCryptContext) {
452
+ *requests(context: MongoCryptContext, timeoutContext?: TimeoutContext) {
436
453
  for (
437
454
  let request = context.nextKMSRequest();
438
455
  request != null;
439
456
  request = context.nextKMSRequest()
440
457
  ) {
441
- yield this.kmsRequest(request);
458
+ yield this.kmsRequest(request, timeoutContext);
442
459
  }
443
460
  }
444
461
 
@@ -498,17 +515,21 @@ export class StateMachine {
498
515
  async fetchCollectionInfo(
499
516
  client: MongoClient,
500
517
  ns: string,
501
- filter: Document
518
+ filter: Document,
519
+ timeoutContext?: TimeoutContext
502
520
  ): Promise<Uint8Array | null> {
503
521
  const { db } = MongoDBCollectionNamespace.fromString(ns);
504
522
 
505
- const collections = await client
506
- .db(db)
507
- .listCollections(filter, {
508
- promoteLongs: false,
509
- promoteValues: false
510
- })
511
- .toArray();
523
+ const cursor = client.db(db).listCollections(filter, {
524
+ promoteLongs: false,
525
+ promoteValues: false,
526
+ timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
527
+ });
528
+
529
+ // There is always exactly zero or one matching documents, so this should always exhaust the cursor
530
+ // in a single batch. We call `toArray()` just to be safe and ensure that the cursor is always
531
+ // exhausted and closed.
532
+ const collections = await cursor.toArray();
512
533
 
513
534
  const info = collections.length > 0 ? serialize(collections[0]) : null;
514
535
  return info;
@@ -522,12 +543,22 @@ export class StateMachine {
522
543
  * @param command - The command to execute.
523
544
  * @param callback - Invoked with the serialized and marked bson command, or with an error
524
545
  */
525
- async markCommand(client: MongoClient, ns: string, command: Uint8Array): Promise<Uint8Array> {
526
- const options = { promoteLongs: false, promoteValues: false };
546
+ async markCommand(
547
+ client: MongoClient,
548
+ ns: string,
549
+ command: Uint8Array,
550
+ timeoutContext?: TimeoutContext
551
+ ): Promise<Uint8Array> {
527
552
  const { db } = MongoDBCollectionNamespace.fromString(ns);
528
- const rawCommand = deserialize(command, options);
553
+ const bsonOptions = { promoteLongs: false, promoteValues: false };
554
+ const rawCommand = deserialize(command, bsonOptions);
529
555
 
530
- const response = await client.db(db).command(rawCommand, options);
556
+ const response = await client.db(db).command(rawCommand, {
557
+ ...bsonOptions,
558
+ ...(timeoutContext?.csotEnabled()
559
+ ? { timeoutMS: timeoutContext?.remainingTimeMS }
560
+ : undefined)
561
+ });
531
562
 
532
563
  return serialize(response, this.bsonOptions);
533
564
  }
@@ -543,7 +574,8 @@ export class StateMachine {
543
574
  fetchKeys(
544
575
  client: MongoClient,
545
576
  keyVaultNamespace: string,
546
- filter: Uint8Array
577
+ filter: Uint8Array,
578
+ timeoutContext?: TimeoutContext
547
579
  ): Promise<Array<DataKey>> {
548
580
  const { db: dbName, collection: collectionName } =
549
581
  MongoDBCollectionNamespace.fromString(keyVaultNamespace);
@@ -551,7 +583,9 @@ export class StateMachine {
551
583
  return client
552
584
  .db(dbName)
553
585
  .collection<DataKey>(collectionName, { readConcern: { level: 'majority' } })
554
- .find(deserialize(filter))
586
+ .find(deserialize(filter), {
587
+ timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
588
+ })
555
589
  .toArray();
556
590
  }
557
591
  }
@@ -10,7 +10,9 @@ import { GSSAPICanonicalizationValue } from './gssapi';
10
10
  import type { OIDCCallbackFunction } from './mongodb_oidc';
11
11
  import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './providers';
12
12
 
13
- // https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst
13
+ /**
14
+ * @see https://github.com/mongodb/specifications/blob/master/source/auth/auth.md
15
+ */
14
16
  function getDefaultAuthMechanism(hello: Document | null): AuthMechanism {
15
17
  if (hello) {
16
18
  // If hello contains saslSupportedMechs, use scram-sha-256
@@ -29,7 +31,8 @@ function getDefaultAuthMechanism(hello: Document | null): AuthMechanism {
29
31
  const ALLOWED_ENVIRONMENT_NAMES: AuthMechanismProperties['ENVIRONMENT'][] = [
30
32
  'test',
31
33
  'azure',
32
- 'gcp'
34
+ 'gcp',
35
+ 'k8s'
33
36
  ];
34
37
  const ALLOWED_HOSTS_ERROR = 'Auth mechanism property ALLOWED_HOSTS must be an array of strings.';
35
38
 
@@ -60,7 +63,7 @@ export interface AuthMechanismProperties extends Document {
60
63
  /** A user provided OIDC human interacted callback function. */
61
64
  OIDC_HUMAN_CALLBACK?: OIDCCallbackFunction;
62
65
  /** The OIDC environment. Note that 'test' is for internal use only. */
63
- ENVIRONMENT?: 'test' | 'azure' | 'gcp';
66
+ ENVIRONMENT?: 'test' | 'azure' | 'gcp' | 'k8s';
64
67
  /** Allowed hosts that OIDC auth can connect to. */
65
68
  ALLOWED_HOSTS?: string[];
66
69
  /** The resource token for OIDC auth in Azure and GCP. */
@@ -107,7 +107,7 @@ export class MongoDBAWS extends AuthProvider {
107
107
 
108
108
  if (!ByteUtils.equals(serverNonce.subarray(0, nonce.byteLength), nonce)) {
109
109
  // throw because the serverNonce's leading 32 bytes must equal the client nonce's 32 bytes
110
- // https://github.com/mongodb/specifications/blob/875446db44aade414011731840831f38a6c668df/source/auth/auth.rst#id11
110
+ // https://github.com/mongodb/specifications/blob/master/source/auth/auth.md#conversation-5
111
111
 
112
112
  // TODO(NODE-3483)
113
113
  throw new MongoRuntimeError('Server nonce does not begin with client nonce');
@@ -0,0 +1,38 @@
1
+ import { readFile } from 'fs/promises';
2
+
3
+ import { type AccessToken, MachineWorkflow } from './machine_workflow';
4
+ import { type TokenCache } from './token_cache';
5
+
6
+ /** The fallback file name */
7
+ const FALLBACK_FILENAME = '/var/run/secrets/kubernetes.io/serviceaccount/token';
8
+
9
+ /** The azure environment variable for the file name. */
10
+ const AZURE_FILENAME = 'AZURE_FEDERATED_TOKEN_FILE';
11
+
12
+ /** The AWS environment variable for the file name. */
13
+ const AWS_FILENAME = 'AWS_WEB_IDENTITY_TOKEN_FILE';
14
+
15
+ export class K8SMachineWorkflow extends MachineWorkflow {
16
+ /**
17
+ * Instantiate the machine workflow.
18
+ */
19
+ constructor(cache: TokenCache) {
20
+ super(cache);
21
+ }
22
+
23
+ /**
24
+ * Get the token from the environment.
25
+ */
26
+ async getToken(): Promise<AccessToken> {
27
+ let filename: string;
28
+ if (process.env[AZURE_FILENAME]) {
29
+ filename = process.env[AZURE_FILENAME];
30
+ } else if (process.env[AWS_FILENAME]) {
31
+ filename = process.env[AWS_FILENAME];
32
+ } else {
33
+ filename = FALLBACK_FILENAME;
34
+ }
35
+ const token = await readFile(filename, 'utf8');
36
+ return { access_token: token };
37
+ }
38
+ }
@@ -6,6 +6,7 @@ import { type AuthContext, AuthProvider } from './auth_provider';
6
6
  import type { MongoCredentials } from './mongo_credentials';
7
7
  import { AzureMachineWorkflow } from './mongodb_oidc/azure_machine_workflow';
8
8
  import { GCPMachineWorkflow } from './mongodb_oidc/gcp_machine_workflow';
9
+ import { K8SMachineWorkflow } from './mongodb_oidc/k8s_machine_workflow';
9
10
  import { TokenCache } from './mongodb_oidc/token_cache';
10
11
  import { TokenMachineWorkflow } from './mongodb_oidc/token_machine_workflow';
11
12
 
@@ -88,7 +89,7 @@ export type OIDCCallbackFunction = (params: OIDCCallbackParams) => Promise<OIDCR
88
89
  /** The current version of OIDC implementation. */
89
90
  export const OIDC_VERSION = 1;
90
91
 
91
- type EnvironmentName = 'test' | 'azure' | 'gcp' | undefined;
92
+ type EnvironmentName = 'test' | 'azure' | 'gcp' | 'k8s' | undefined;
92
93
 
93
94
  /** @internal */
94
95
  export interface Workflow {
@@ -118,6 +119,7 @@ export const OIDC_WORKFLOWS: Map<EnvironmentName, () => Workflow> = new Map();
118
119
  OIDC_WORKFLOWS.set('test', () => new TokenMachineWorkflow(new TokenCache()));
119
120
  OIDC_WORKFLOWS.set('azure', () => new AzureMachineWorkflow(new TokenCache()));
120
121
  OIDC_WORKFLOWS.set('gcp', () => new GCPMachineWorkflow(new TokenCache()));
122
+ OIDC_WORKFLOWS.set('k8s', () => new K8SMachineWorkflow(new TokenCache()));
121
123
 
122
124
  /**
123
125
  * OIDC auth provider.
@@ -113,7 +113,8 @@ export async function performInitialHandshake(
113
113
  }
114
114
 
115
115
  const start = new Date().getTime();
116
- const response = await conn.command(ns('admin.$cmd'), handshakeDoc, handshakeOptions);
116
+
117
+ const response = await executeHandshake(handshakeDoc, handshakeOptions);
117
118
 
118
119
  if (!('isWritablePrimary' in response)) {
119
120
  // Provide hello-style response document.
@@ -175,6 +176,22 @@ export async function performInitialHandshake(
175
176
  // Connection establishment is socket creation (tcp handshake, tls handshake, MongoDB handshake (saslStart, saslContinue))
176
177
  // Once connection is established, command logging can log events (if enabled)
177
178
  conn.established = true;
179
+
180
+ async function executeHandshake(handshakeDoc: Document, handshakeOptions: CommandOptions) {
181
+ try {
182
+ const handshakeResponse = await conn.command(
183
+ ns('admin.$cmd'),
184
+ handshakeDoc,
185
+ handshakeOptions
186
+ );
187
+ return handshakeResponse;
188
+ } catch (error) {
189
+ if (error instanceof MongoError) {
190
+ error.addErrorLabel(MongoErrorLabel.HandshakeError);
191
+ }
192
+ throw error;
193
+ }
194
+ }
178
195
  }
179
196
 
180
197
  /**