mongodb 5.1.0 → 5.2.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 (120) hide show
  1. package/README.md +25 -22
  2. package/lib/change_stream.js +1 -1
  3. package/lib/cmap/auth/auth_provider.js +21 -10
  4. package/lib/cmap/auth/auth_provider.js.map +1 -1
  5. package/lib/cmap/auth/gssapi.js +71 -116
  6. package/lib/cmap/auth/gssapi.js.map +1 -1
  7. package/lib/cmap/auth/mongo_credentials.js +7 -9
  8. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  9. package/lib/cmap/auth/mongocr.js +20 -29
  10. package/lib/cmap/auth/mongocr.js.map +1 -1
  11. package/lib/cmap/auth/mongodb_aws.js +125 -140
  12. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  13. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +28 -0
  14. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -0
  15. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +178 -0
  16. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -0
  17. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +41 -0
  18. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -0
  19. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +115 -0
  20. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -0
  21. package/lib/cmap/auth/mongodb_oidc/workflow.js +3 -0
  22. package/lib/cmap/auth/mongodb_oidc/workflow.js.map +1 -0
  23. package/lib/cmap/auth/mongodb_oidc.js +59 -0
  24. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  25. package/lib/cmap/auth/plain.js +4 -5
  26. package/lib/cmap/auth/plain.js.map +1 -1
  27. package/lib/cmap/auth/providers.js +1 -1
  28. package/lib/cmap/auth/providers.js.map +1 -1
  29. package/lib/cmap/auth/scram.js +45 -73
  30. package/lib/cmap/auth/scram.js.map +1 -1
  31. package/lib/cmap/auth/x509.js +8 -11
  32. package/lib/cmap/auth/x509.js.map +1 -1
  33. package/lib/cmap/command_monitoring_events.js +8 -5
  34. package/lib/cmap/command_monitoring_events.js.map +1 -1
  35. package/lib/cmap/commands.js +1 -1
  36. package/lib/cmap/commands.js.map +1 -1
  37. package/lib/cmap/connect.js +72 -86
  38. package/lib/cmap/connect.js.map +1 -1
  39. package/lib/cmap/connection.js +19 -23
  40. package/lib/cmap/connection.js.map +1 -1
  41. package/lib/cmap/connection_pool.js +49 -11
  42. package/lib/cmap/connection_pool.js.map +1 -1
  43. package/lib/cmap/message_stream.js.map +1 -1
  44. package/lib/cmap/wire_protocol/shared.js +1 -16
  45. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  46. package/lib/collection.js +10 -10
  47. package/lib/connection_string.js +11 -17
  48. package/lib/connection_string.js.map +1 -1
  49. package/lib/cursor/abstract_cursor.js +1 -1
  50. package/lib/cursor/find_cursor.js +1 -1
  51. package/lib/db.js +2 -2
  52. package/lib/error.js +2 -1
  53. package/lib/error.js.map +1 -1
  54. package/lib/mongo_client.js +22 -2
  55. package/lib/mongo_client.js.map +1 -1
  56. package/lib/mongo_logger.js +17 -1
  57. package/lib/mongo_logger.js.map +1 -1
  58. package/lib/operations/execute_operation.js +8 -27
  59. package/lib/operations/execute_operation.js.map +1 -1
  60. package/lib/operations/find.js +1 -1
  61. package/lib/read_concern.js +1 -1
  62. package/lib/read_preference.js +2 -2
  63. package/lib/sdam/topology.js +0 -16
  64. package/lib/sdam/topology.js.map +1 -1
  65. package/lib/utils.js +15 -70
  66. package/lib/utils.js.map +1 -1
  67. package/lib/write_concern.js +1 -1
  68. package/mongodb.d.ts +103 -70
  69. package/package.json +29 -30
  70. package/src/bulk/common.ts +1 -1
  71. package/src/change_stream.ts +5 -5
  72. package/src/cmap/auth/auth_provider.ts +29 -16
  73. package/src/cmap/auth/gssapi.ts +102 -149
  74. package/src/cmap/auth/mongo_credentials.ts +14 -23
  75. package/src/cmap/auth/mongocr.ts +31 -36
  76. package/src/cmap/auth/mongodb_aws.ts +166 -189
  77. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +26 -0
  78. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +259 -0
  79. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +47 -0
  80. package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +166 -0
  81. package/src/cmap/auth/mongodb_oidc/workflow.ts +21 -0
  82. package/src/cmap/auth/mongodb_oidc.ts +101 -17
  83. package/src/cmap/auth/plain.ts +6 -6
  84. package/src/cmap/auth/providers.ts +2 -2
  85. package/src/cmap/auth/scram.ts +56 -90
  86. package/src/cmap/auth/x509.ts +12 -18
  87. package/src/cmap/command_monitoring_events.ts +5 -2
  88. package/src/cmap/commands.ts +1 -1
  89. package/src/cmap/connect.ts +90 -114
  90. package/src/cmap/connection.ts +36 -24
  91. package/src/cmap/connection_pool.ts +75 -14
  92. package/src/cmap/message_stream.ts +0 -2
  93. package/src/cmap/wire_protocol/compression.ts +1 -1
  94. package/src/cmap/wire_protocol/shared.ts +1 -23
  95. package/src/collection.ts +10 -10
  96. package/src/connection_string.ts +12 -17
  97. package/src/cursor/abstract_cursor.ts +2 -2
  98. package/src/cursor/change_stream_cursor.ts +5 -5
  99. package/src/cursor/find_cursor.ts +1 -1
  100. package/src/db.ts +2 -2
  101. package/src/deps.ts +3 -2
  102. package/src/error.ts +3 -2
  103. package/src/index.ts +1 -0
  104. package/src/mongo_client.ts +35 -10
  105. package/src/mongo_logger.ts +20 -2
  106. package/src/mongo_types.ts +2 -2
  107. package/src/operations/aggregate.ts +1 -1
  108. package/src/operations/create_collection.ts +1 -1
  109. package/src/operations/execute_operation.ts +8 -25
  110. package/src/operations/find.ts +1 -1
  111. package/src/operations/find_and_modify.ts +4 -4
  112. package/src/operations/set_profiling_level.ts +1 -1
  113. package/src/operations/stats.ts +1 -1
  114. package/src/read_concern.ts +2 -2
  115. package/src/read_preference.ts +3 -3
  116. package/src/sdam/common.ts +2 -2
  117. package/src/sdam/topology.ts +0 -20
  118. package/src/transactions.ts +1 -1
  119. package/src/utils.ts +24 -98
  120. package/src/write_concern.ts +1 -1
@@ -1,15 +1,14 @@
1
1
  import * as crypto from 'crypto';
2
+ import { promisify } from 'util';
2
3
 
3
4
  import { Binary, Document } from '../../bson';
4
5
  import { saslprep } from '../../deps';
5
6
  import {
6
- AnyError,
7
7
  MongoInvalidArgumentError,
8
8
  MongoMissingCredentialsError,
9
- MongoRuntimeError,
10
- MongoServerError
9
+ MongoRuntimeError
11
10
  } from '../../error';
12
- import { Callback, emitWarning, ns } from '../../utils';
11
+ import { emitWarning, ns } from '../../utils';
13
12
  import type { HandshakeDocument } from '../connect';
14
13
  import { AuthContext, AuthProvider } from './auth_provider';
15
14
  import type { MongoCredentials } from './mongo_credentials';
@@ -19,53 +18,51 @@ type CryptoMethod = 'sha1' | 'sha256';
19
18
 
20
19
  class ScramSHA extends AuthProvider {
21
20
  cryptoMethod: CryptoMethod;
21
+ randomBytesAsync: (size: number) => Promise<Buffer>;
22
22
  constructor(cryptoMethod: CryptoMethod) {
23
23
  super();
24
24
  this.cryptoMethod = cryptoMethod || 'sha1';
25
+ this.randomBytesAsync = promisify(crypto.randomBytes);
25
26
  }
26
27
 
27
- override prepare(handshakeDoc: HandshakeDocument, authContext: AuthContext, callback: Callback) {
28
+ override async prepare(
29
+ handshakeDoc: HandshakeDocument,
30
+ authContext: AuthContext
31
+ ): Promise<HandshakeDocument> {
28
32
  const cryptoMethod = this.cryptoMethod;
29
33
  const credentials = authContext.credentials;
30
34
  if (!credentials) {
31
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
35
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
32
36
  }
33
37
  if (cryptoMethod === 'sha256' && saslprep == null) {
34
38
  emitWarning('Warning: no saslprep library specified. Passwords will not be sanitized');
35
39
  }
36
40
 
37
- crypto.randomBytes(24, (err, nonce) => {
38
- if (err) {
39
- return callback(err);
40
- }
41
-
42
- // store the nonce for later use
43
- Object.assign(authContext, { nonce });
41
+ const nonce = await this.randomBytesAsync(24);
42
+ // store the nonce for later use
43
+ authContext.nonce = nonce;
44
44
 
45
- const request = Object.assign({}, handshakeDoc, {
46
- speculativeAuthenticate: Object.assign(makeFirstMessage(cryptoMethod, credentials, nonce), {
47
- db: credentials.source
48
- })
49
- });
45
+ const request = {
46
+ ...handshakeDoc,
47
+ speculativeAuthenticate: {
48
+ ...makeFirstMessage(cryptoMethod, credentials, nonce),
49
+ db: credentials.source
50
+ }
51
+ };
50
52
 
51
- callback(undefined, request);
52
- });
53
+ return request;
53
54
  }
54
55
 
55
- override auth(authContext: AuthContext, callback: Callback) {
56
- const response = authContext.response;
57
- if (response && response.speculativeAuthenticate) {
58
- continueScramConversation(
56
+ override async auth(authContext: AuthContext) {
57
+ const { reauthenticating, response } = authContext;
58
+ if (response?.speculativeAuthenticate && !reauthenticating) {
59
+ return continueScramConversation(
59
60
  this.cryptoMethod,
60
61
  response.speculativeAuthenticate,
61
- authContext,
62
- callback
62
+ authContext
63
63
  );
64
-
65
- return;
66
64
  }
67
-
68
- executeScram(this.cryptoMethod, authContext, callback);
65
+ return executeScram(this.cryptoMethod, authContext);
69
66
  }
70
67
  }
71
68
 
@@ -106,43 +103,34 @@ function makeFirstMessage(
106
103
  };
107
104
  }
108
105
 
109
- function executeScram(cryptoMethod: CryptoMethod, authContext: AuthContext, callback: Callback) {
106
+ async function executeScram(cryptoMethod: CryptoMethod, authContext: AuthContext): Promise<void> {
110
107
  const { connection, credentials } = authContext;
111
108
  if (!credentials) {
112
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
109
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
113
110
  }
114
111
  if (!authContext.nonce) {
115
- return callback(
116
- new MongoInvalidArgumentError('AuthContext must contain a valid nonce property')
117
- );
112
+ throw new MongoInvalidArgumentError('AuthContext must contain a valid nonce property');
118
113
  }
119
114
  const nonce = authContext.nonce;
120
115
  const db = credentials.source;
121
116
 
122
117
  const saslStartCmd = makeFirstMessage(cryptoMethod, credentials, nonce);
123
- connection.command(ns(`${db}.$cmd`), saslStartCmd, undefined, (_err, result) => {
124
- const err = resolveError(_err, result);
125
- if (err) {
126
- return callback(err);
127
- }
128
-
129
- continueScramConversation(cryptoMethod, result, authContext, callback);
130
- });
118
+ const response = await connection.commandAsync(ns(`${db}.$cmd`), saslStartCmd, undefined);
119
+ await continueScramConversation(cryptoMethod, response, authContext);
131
120
  }
132
121
 
133
- function continueScramConversation(
122
+ async function continueScramConversation(
134
123
  cryptoMethod: CryptoMethod,
135
124
  response: Document,
136
- authContext: AuthContext,
137
- callback: Callback
138
- ) {
125
+ authContext: AuthContext
126
+ ): Promise<void> {
139
127
  const connection = authContext.connection;
140
128
  const credentials = authContext.credentials;
141
129
  if (!credentials) {
142
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
130
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
143
131
  }
144
132
  if (!authContext.nonce) {
145
- return callback(new MongoInvalidArgumentError('Unable to continue SCRAM without valid nonce'));
133
+ throw new MongoInvalidArgumentError('Unable to continue SCRAM without valid nonce');
146
134
  }
147
135
  const nonce = authContext.nonce;
148
136
 
@@ -154,11 +142,7 @@ function continueScramConversation(
154
142
  if (cryptoMethod === 'sha256') {
155
143
  processedPassword = 'kModuleError' in saslprep ? password : saslprep(password);
156
144
  } else {
157
- try {
158
- processedPassword = passwordDigest(username, password);
159
- } catch (e) {
160
- return callback(e);
161
- }
145
+ processedPassword = passwordDigest(username, password);
162
146
  }
163
147
 
164
148
  const payload = Buffer.isBuffer(response.payload)
@@ -168,20 +152,15 @@ function continueScramConversation(
168
152
 
169
153
  const iterations = parseInt(dict.i, 10);
170
154
  if (iterations && iterations < 4096) {
171
- callback(
172
- // TODO(NODE-3483)
173
- new MongoRuntimeError(`Server returned an invalid iteration count ${iterations}`),
174
- false
175
- );
176
- return;
155
+ // TODO(NODE-3483)
156
+ throw new MongoRuntimeError(`Server returned an invalid iteration count ${iterations}`);
177
157
  }
178
158
 
179
159
  const salt = dict.s;
180
160
  const rnonce = dict.r;
181
161
  if (rnonce.startsWith('nonce')) {
182
162
  // TODO(NODE-3483)
183
- callback(new MongoRuntimeError(`Server returned an invalid nonce: ${rnonce}`), false);
184
- return;
163
+ throw new MongoRuntimeError(`Server returned an invalid nonce: ${rnonce}`);
185
164
  }
186
165
 
187
166
  // Set up start of proof
@@ -211,30 +190,25 @@ function continueScramConversation(
211
190
  payload: new Binary(Buffer.from(clientFinal))
212
191
  };
213
192
 
214
- connection.command(ns(`${db}.$cmd`), saslContinueCmd, undefined, (_err, r) => {
215
- const err = resolveError(_err, r);
216
- if (err) {
217
- return callback(err);
218
- }
193
+ const r = await connection.commandAsync(ns(`${db}.$cmd`), saslContinueCmd, undefined);
194
+ const parsedResponse = parsePayload(r.payload.value());
219
195
 
220
- const parsedResponse = parsePayload(r.payload.value());
221
- if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) {
222
- callback(new MongoRuntimeError('Server returned an invalid signature'));
223
- return;
224
- }
196
+ if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) {
197
+ throw new MongoRuntimeError('Server returned an invalid signature');
198
+ }
225
199
 
226
- if (!r || r.done !== false) {
227
- return callback(err, r);
228
- }
200
+ if (r.done !== false) {
201
+ // If the server sends r.done === true we can save one RTT
202
+ return;
203
+ }
229
204
 
230
- const retrySaslContinueCmd = {
231
- saslContinue: 1,
232
- conversationId: r.conversationId,
233
- payload: Buffer.alloc(0)
234
- };
205
+ const retrySaslContinueCmd = {
206
+ saslContinue: 1,
207
+ conversationId: r.conversationId,
208
+ payload: Buffer.alloc(0)
209
+ };
235
210
 
236
- connection.command(ns(`${db}.$cmd`), retrySaslContinueCmd, undefined, callback);
237
- });
211
+ await connection.commandAsync(ns(`${db}.$cmd`), retrySaslContinueCmd, undefined);
238
212
  }
239
213
 
240
214
  function parsePayload(payload: string) {
@@ -363,14 +337,6 @@ function compareDigest(lhs: Buffer, rhs: Uint8Array) {
363
337
  return result === 0;
364
338
  }
365
339
 
366
- function resolveError(err?: AnyError, result?: Document) {
367
- if (err) return err;
368
- if (result) {
369
- if (result.$err || result.errmsg) return new MongoServerError(result);
370
- }
371
- return;
372
- }
373
-
374
340
  export class ScramSHA1 extends ScramSHA {
375
341
  constructor() {
376
342
  super('sha1');
@@ -1,44 +1,38 @@
1
1
  import type { Document } from '../../bson';
2
2
  import { MongoMissingCredentialsError } from '../../error';
3
- import { Callback, ns } from '../../utils';
3
+ import { ns } from '../../utils';
4
4
  import type { HandshakeDocument } from '../connect';
5
5
  import { AuthContext, AuthProvider } from './auth_provider';
6
6
  import type { MongoCredentials } from './mongo_credentials';
7
7
 
8
8
  export class X509 extends AuthProvider {
9
- override prepare(
9
+ override async prepare(
10
10
  handshakeDoc: HandshakeDocument,
11
- authContext: AuthContext,
12
- callback: Callback
13
- ): void {
11
+ authContext: AuthContext
12
+ ): Promise<HandshakeDocument> {
14
13
  const { credentials } = authContext;
15
14
  if (!credentials) {
16
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
15
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
17
16
  }
18
- Object.assign(handshakeDoc, {
19
- speculativeAuthenticate: x509AuthenticateCommand(credentials)
20
- });
21
-
22
- callback(undefined, handshakeDoc);
17
+ return { ...handshakeDoc, speculativeAuthenticate: x509AuthenticateCommand(credentials) };
23
18
  }
24
19
 
25
- override auth(authContext: AuthContext, callback: Callback): void {
20
+ override async auth(authContext: AuthContext) {
26
21
  const connection = authContext.connection;
27
22
  const credentials = authContext.credentials;
28
23
  if (!credentials) {
29
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
24
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
30
25
  }
31
26
  const response = authContext.response;
32
27
 
33
- if (response && response.speculativeAuthenticate) {
34
- return callback();
28
+ if (response?.speculativeAuthenticate) {
29
+ return;
35
30
  }
36
31
 
37
- connection.command(
32
+ await connection.commandAsync(
38
33
  ns('$external.$cmd'),
39
34
  x509AuthenticateCommand(credentials),
40
- undefined,
41
- callback
35
+ undefined
42
36
  );
43
37
  }
44
38
  }
@@ -149,8 +149,11 @@ export class CommandFailedEvent {
149
149
  }
150
150
  }
151
151
 
152
- /** Commands that we want to redact because of the sensitive nature of their contents */
153
- const SENSITIVE_COMMANDS = new Set([
152
+ /**
153
+ * Commands that we want to redact because of the sensitive nature of their contents
154
+ * @internal
155
+ */
156
+ export const SENSITIVE_COMMANDS = new Set([
154
157
  'authenticate',
155
158
  'saslStart',
156
159
  'saslContinue',
@@ -374,7 +374,7 @@ export class Response {
374
374
  };
375
375
 
376
376
  // Position within OP_REPLY at which documents start
377
- // (See https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#wire-op-reply)
377
+ // (See https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/#wire-op-reply)
378
378
  this.index = 20;
379
379
 
380
380
  // Read the message body
@@ -15,19 +15,19 @@ import {
15
15
  MongoNetworkError,
16
16
  MongoNetworkTimeoutError,
17
17
  MongoRuntimeError,
18
- MongoServerError,
19
18
  needsRetryableWriteLabel
20
19
  } from '../error';
21
- import { Callback, ClientMetadata, HostAddress, makeClientMetadata, ns } from '../utils';
20
+ import { Callback, ClientMetadata, HostAddress, ns } from '../utils';
22
21
  import { AuthContext, AuthProvider } from './auth/auth_provider';
23
22
  import { GSSAPI } from './auth/gssapi';
24
23
  import { MongoCR } from './auth/mongocr';
25
24
  import { MongoDBAWS } from './auth/mongodb_aws';
25
+ import { MongoDBOIDC } from './auth/mongodb_oidc';
26
26
  import { Plain } from './auth/plain';
27
27
  import { AuthMechanism } from './auth/providers';
28
28
  import { ScramSHA1, ScramSHA256 } from './auth/scram';
29
29
  import { X509 } from './auth/x509';
30
- import { Connection, ConnectionOptions, CryptoConnection } from './connection';
30
+ import { CommandOptions, Connection, ConnectionOptions, CryptoConnection } from './connection';
31
31
  import {
32
32
  MAX_SUPPORTED_SERVER_VERSION,
33
33
  MAX_SUPPORTED_WIRE_VERSION,
@@ -35,10 +35,12 @@ import {
35
35
  MIN_SUPPORTED_WIRE_VERSION
36
36
  } from './wire_protocol/constants';
37
37
 
38
- const AUTH_PROVIDERS = new Map<AuthMechanism | string, AuthProvider>([
38
+ /** @internal */
39
+ export const AUTH_PROVIDERS = new Map<AuthMechanism | string, AuthProvider>([
39
40
  [AuthMechanism.MONGODB_AWS, new MongoDBAWS()],
40
41
  [AuthMechanism.MONGODB_CR, new MongoCR()],
41
42
  [AuthMechanism.MONGODB_GSSAPI, new GSSAPI()],
43
+ [AuthMechanism.MONGODB_OIDC, new MongoDBOIDC()],
42
44
  [AuthMechanism.MONGODB_PLAIN, new Plain()],
43
45
  [AuthMechanism.MONGODB_SCRAM_SHA1, new ScramSHA1()],
44
46
  [AuthMechanism.MONGODB_SCRAM_SHA256, new ScramSHA256()],
@@ -58,7 +60,16 @@ export function connect(options: ConnectionOptions, callback: Callback<Connectio
58
60
  if (options.autoEncrypter) {
59
61
  ConnectionType = CryptoConnection;
60
62
  }
61
- performInitialHandshake(new ConnectionType(socket, options), options, callback);
63
+
64
+ const connection = new ConnectionType(socket, options);
65
+
66
+ performInitialHandshake(connection, options).then(
67
+ () => callback(undefined, connection),
68
+ error => {
69
+ connection.destroy({ force: false });
70
+ callback(error);
71
+ }
72
+ );
62
73
  });
63
74
  }
64
75
 
@@ -89,119 +100,89 @@ function checkSupportedServer(hello: Document, options: ConnectionOptions) {
89
100
  return new MongoCompatibilityError(message);
90
101
  }
91
102
 
92
- function performInitialHandshake(
103
+ async function performInitialHandshake(
93
104
  conn: Connection,
94
- options: ConnectionOptions,
95
- _callback: Callback
96
- ) {
97
- const callback: Callback<Document> = function (err, ret) {
98
- if (err && conn) {
99
- conn.destroy({ force: false });
100
- }
101
- _callback(err, ret);
102
- };
103
-
105
+ options: ConnectionOptions
106
+ ): Promise<void> {
104
107
  const credentials = options.credentials;
108
+
105
109
  if (credentials) {
106
110
  if (
107
111
  !(credentials.mechanism === AuthMechanism.MONGODB_DEFAULT) &&
108
112
  !AUTH_PROVIDERS.get(credentials.mechanism)
109
113
  ) {
110
- callback(
111
- new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`)
112
- );
113
- return;
114
+ throw new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`);
114
115
  }
115
116
  }
116
117
 
117
118
  const authContext = new AuthContext(conn, credentials, options);
118
- prepareHandshakeDocument(authContext, (err, handshakeDoc) => {
119
- if (err || !handshakeDoc) {
120
- return callback(err);
121
- }
119
+ conn.authContext = authContext;
122
120
 
123
- const handshakeOptions: Document = Object.assign({}, options);
124
- if (typeof options.connectTimeoutMS === 'number') {
125
- // The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
126
- handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
127
- }
121
+ const handshakeDoc = await prepareHandshakeDocument(authContext);
128
122
 
129
- const start = new Date().getTime();
130
- conn.command(ns('admin.$cmd'), handshakeDoc, handshakeOptions, (err, response) => {
131
- if (err) {
132
- callback(err);
133
- return;
134
- }
123
+ // @ts-expect-error: TODO(NODE-5141): The options need to be filtered properly, Connection options differ from Command options
124
+ const handshakeOptions: CommandOptions = { ...options };
125
+ if (typeof options.connectTimeoutMS === 'number') {
126
+ // The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
127
+ handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
128
+ }
135
129
 
136
- if (response?.ok === 0) {
137
- callback(new MongoServerError(response));
138
- return;
139
- }
130
+ const start = new Date().getTime();
131
+ const response = await conn.commandAsync(ns('admin.$cmd'), handshakeDoc, handshakeOptions);
140
132
 
141
- if (!('isWritablePrimary' in response)) {
142
- // Provide hello-style response document.
143
- response.isWritablePrimary = response[LEGACY_HELLO_COMMAND];
144
- }
133
+ if (!('isWritablePrimary' in response)) {
134
+ // Provide hello-style response document.
135
+ response.isWritablePrimary = response[LEGACY_HELLO_COMMAND];
136
+ }
145
137
 
146
- if (response.helloOk) {
147
- conn.helloOk = true;
148
- }
138
+ if (response.helloOk) {
139
+ conn.helloOk = true;
140
+ }
149
141
 
150
- const supportedServerErr = checkSupportedServer(response, options);
151
- if (supportedServerErr) {
152
- callback(supportedServerErr);
153
- return;
154
- }
142
+ const supportedServerErr = checkSupportedServer(response, options);
143
+ if (supportedServerErr) {
144
+ throw supportedServerErr;
145
+ }
155
146
 
156
- if (options.loadBalanced) {
157
- if (!response.serviceId) {
158
- return callback(
159
- new MongoCompatibilityError(
160
- 'Driver attempted to initialize in load balancing mode, ' +
161
- 'but the server does not support this mode.'
162
- )
163
- );
164
- }
165
- }
147
+ if (options.loadBalanced) {
148
+ if (!response.serviceId) {
149
+ throw new MongoCompatibilityError(
150
+ 'Driver attempted to initialize in load balancing mode, ' +
151
+ 'but the server does not support this mode.'
152
+ );
153
+ }
154
+ }
166
155
 
167
- // NOTE: This is metadata attached to the connection while porting away from
168
- // handshake being done in the `Server` class. Likely, it should be
169
- // relocated, or at very least restructured.
170
- conn.hello = response;
171
- conn.lastHelloMS = new Date().getTime() - start;
172
-
173
- if (!response.arbiterOnly && credentials) {
174
- // store the response on auth context
175
- authContext.response = response;
176
-
177
- const resolvedCredentials = credentials.resolveAuthMechanism(response);
178
- const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism);
179
- if (!provider) {
180
- return callback(
181
- new MongoInvalidArgumentError(
182
- `No AuthProvider for ${resolvedCredentials.mechanism} defined.`
183
- )
184
- );
156
+ // NOTE: This is metadata attached to the connection while porting away from
157
+ // handshake being done in the `Server` class. Likely, it should be
158
+ // relocated, or at very least restructured.
159
+ conn.hello = response;
160
+ conn.lastHelloMS = new Date().getTime() - start;
161
+
162
+ if (!response.arbiterOnly && credentials) {
163
+ // store the response on auth context
164
+ authContext.response = response;
165
+
166
+ const resolvedCredentials = credentials.resolveAuthMechanism(response);
167
+ const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism);
168
+ if (!provider) {
169
+ throw new MongoInvalidArgumentError(
170
+ `No AuthProvider for ${resolvedCredentials.mechanism} defined.`
171
+ );
172
+ }
173
+
174
+ try {
175
+ await provider.auth(authContext);
176
+ } catch (error) {
177
+ if (error instanceof MongoError) {
178
+ error.addErrorLabel(MongoErrorLabel.HandshakeError);
179
+ if (needsRetryableWriteLabel(error, response.maxWireVersion)) {
180
+ error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
185
181
  }
186
- provider.auth(authContext, err => {
187
- if (err) {
188
- if (err instanceof MongoError) {
189
- err.addErrorLabel(MongoErrorLabel.HandshakeError);
190
- if (needsRetryableWriteLabel(err, response.maxWireVersion)) {
191
- err.addErrorLabel(MongoErrorLabel.RetryableWriteError);
192
- }
193
- }
194
- return callback(err);
195
- }
196
- callback(undefined, conn);
197
- });
198
-
199
- return;
200
182
  }
201
-
202
- callback(undefined, conn);
203
- });
204
- });
183
+ throw error;
184
+ }
185
+ }
205
186
  }
206
187
 
207
188
  export interface HandshakeDocument extends Document {
@@ -222,10 +203,9 @@ export interface HandshakeDocument extends Document {
222
203
  *
223
204
  * This function is only exposed for testing purposes.
224
205
  */
225
- export function prepareHandshakeDocument(
226
- authContext: AuthContext,
227
- callback: Callback<HandshakeDocument>
228
- ) {
206
+ export async function prepareHandshakeDocument(
207
+ authContext: AuthContext
208
+ ): Promise<HandshakeDocument> {
229
209
  const options = authContext.options;
230
210
  const compressors = options.compressors ? options.compressors : [];
231
211
  const { serverApi } = authContext.connection;
@@ -233,7 +213,7 @@ export function prepareHandshakeDocument(
233
213
  const handshakeDoc: HandshakeDocument = {
234
214
  [serverApi?.version ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
235
215
  helloOk: true,
236
- client: options.metadata || makeClientMetadata(options),
216
+ client: options.metadata,
237
217
  compression: compressors
238
218
  };
239
219
 
@@ -249,23 +229,19 @@ export function prepareHandshakeDocument(
249
229
  const provider = AUTH_PROVIDERS.get(AuthMechanism.MONGODB_SCRAM_SHA256);
250
230
  if (!provider) {
251
231
  // This auth mechanism is always present.
252
- return callback(
253
- new MongoInvalidArgumentError(
254
- `No AuthProvider for ${AuthMechanism.MONGODB_SCRAM_SHA256} defined.`
255
- )
232
+ throw new MongoInvalidArgumentError(
233
+ `No AuthProvider for ${AuthMechanism.MONGODB_SCRAM_SHA256} defined.`
256
234
  );
257
235
  }
258
- return provider.prepare(handshakeDoc, authContext, callback);
236
+ return provider.prepare(handshakeDoc, authContext);
259
237
  }
260
238
  const provider = AUTH_PROVIDERS.get(credentials.mechanism);
261
239
  if (!provider) {
262
- return callback(
263
- new MongoInvalidArgumentError(`No AuthProvider for ${credentials.mechanism} defined.`)
264
- );
240
+ throw new MongoInvalidArgumentError(`No AuthProvider for ${credentials.mechanism} defined.`);
265
241
  }
266
- return provider.prepare(handshakeDoc, authContext, callback);
242
+ return provider.prepare(handshakeDoc, authContext);
267
243
  }
268
- callback(undefined, handshakeDoc);
244
+ return handshakeDoc;
269
245
  }
270
246
 
271
247
  /** @public */
@@ -347,7 +323,7 @@ function parseSslOptions(options: MakeConnectionOptions): TLSConnectionOpts {
347
323
  }
348
324
 
349
325
  const SOCKET_ERROR_EVENT_LIST = ['error', 'close', 'timeout', 'parseError'] as const;
350
- type ErrorHandlerEventName = typeof SOCKET_ERROR_EVENT_LIST[number] | 'cancel';
326
+ type ErrorHandlerEventName = (typeof SOCKET_ERROR_EVENT_LIST)[number] | 'cancel';
351
327
  const SOCKET_ERROR_EVENTS = new Set(SOCKET_ERROR_EVENT_LIST);
352
328
 
353
329
  function makeConnection(options: MakeConnectionOptions, _callback: Callback<Stream>) {
@@ -428,7 +404,7 @@ function makeConnection(options: MakeConnectionOptions, _callback: Callback<Stre
428
404
  }
429
405
  }
430
406
 
431
- socket.setTimeout(socketTimeoutMS);
407
+ socket.setTimeout(0);
432
408
  callback(undefined, socket);
433
409
  }
434
410