mongodb 5.0.1 → 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 (146) hide show
  1. package/README.md +25 -22
  2. package/lib/bson.js +3 -1
  3. package/lib/bson.js.map +1 -1
  4. package/lib/change_stream.js +3 -2
  5. package/lib/change_stream.js.map +1 -1
  6. package/lib/cmap/auth/auth_provider.js +21 -10
  7. package/lib/cmap/auth/auth_provider.js.map +1 -1
  8. package/lib/cmap/auth/gssapi.js +71 -116
  9. package/lib/cmap/auth/gssapi.js.map +1 -1
  10. package/lib/cmap/auth/mongo_credentials.js +17 -0
  11. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  12. package/lib/cmap/auth/mongocr.js +20 -29
  13. package/lib/cmap/auth/mongocr.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_aws.js +126 -140
  15. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  16. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +28 -0
  17. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -0
  18. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +178 -0
  19. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -0
  20. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +41 -0
  21. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -0
  22. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +115 -0
  23. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -0
  24. package/lib/cmap/auth/mongodb_oidc/workflow.js +3 -0
  25. package/lib/cmap/auth/mongodb_oidc/workflow.js.map +1 -0
  26. package/lib/cmap/auth/mongodb_oidc.js +62 -0
  27. package/lib/cmap/auth/mongodb_oidc.js.map +1 -0
  28. package/lib/cmap/auth/plain.js +4 -5
  29. package/lib/cmap/auth/plain.js.map +1 -1
  30. package/lib/cmap/auth/providers.js +4 -1
  31. package/lib/cmap/auth/providers.js.map +1 -1
  32. package/lib/cmap/auth/scram.js +45 -73
  33. package/lib/cmap/auth/scram.js.map +1 -1
  34. package/lib/cmap/auth/x509.js +8 -11
  35. package/lib/cmap/auth/x509.js.map +1 -1
  36. package/lib/cmap/command_monitoring_events.js +8 -5
  37. package/lib/cmap/command_monitoring_events.js.map +1 -1
  38. package/lib/cmap/commands.js +9 -1
  39. package/lib/cmap/commands.js.map +1 -1
  40. package/lib/cmap/connect.js +72 -86
  41. package/lib/cmap/connect.js.map +1 -1
  42. package/lib/cmap/connection.js +68 -74
  43. package/lib/cmap/connection.js.map +1 -1
  44. package/lib/cmap/connection_pool.js +51 -13
  45. package/lib/cmap/connection_pool.js.map +1 -1
  46. package/lib/cmap/message_stream.js.map +1 -1
  47. package/lib/cmap/wire_protocol/shared.js +1 -16
  48. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  49. package/lib/collection.js +10 -10
  50. package/lib/connection_string.js +47 -33
  51. package/lib/connection_string.js.map +1 -1
  52. package/lib/cursor/abstract_cursor.js +13 -7
  53. package/lib/cursor/abstract_cursor.js.map +1 -1
  54. package/lib/cursor/find_cursor.js +1 -1
  55. package/lib/db.js +3 -2
  56. package/lib/db.js.map +1 -1
  57. package/lib/error.js +2 -1
  58. package/lib/error.js.map +1 -1
  59. package/lib/mongo_client.js +22 -2
  60. package/lib/mongo_client.js.map +1 -1
  61. package/lib/mongo_logger.js +17 -1
  62. package/lib/mongo_logger.js.map +1 -1
  63. package/lib/operations/aggregate.js +4 -1
  64. package/lib/operations/aggregate.js.map +1 -1
  65. package/lib/operations/create_collection.js +1 -0
  66. package/lib/operations/create_collection.js.map +1 -1
  67. package/lib/operations/execute_operation.js +8 -27
  68. package/lib/operations/execute_operation.js.map +1 -1
  69. package/lib/operations/find.js +3 -2
  70. package/lib/operations/find.js.map +1 -1
  71. package/lib/operations/indexes.js +2 -1
  72. package/lib/operations/indexes.js.map +1 -1
  73. package/lib/operations/list_collections.js +2 -1
  74. package/lib/operations/list_collections.js.map +1 -1
  75. package/lib/read_concern.js +1 -1
  76. package/lib/read_preference.js +2 -2
  77. package/lib/sdam/monitor.js +1 -0
  78. package/lib/sdam/monitor.js.map +1 -1
  79. package/lib/sdam/server.js +4 -2
  80. package/lib/sdam/server.js.map +1 -1
  81. package/lib/sdam/topology.js +3 -26
  82. package/lib/sdam/topology.js.map +1 -1
  83. package/lib/sessions.js +2 -1
  84. package/lib/sessions.js.map +1 -1
  85. package/lib/utils.js +15 -70
  86. package/lib/utils.js.map +1 -1
  87. package/lib/write_concern.js +1 -1
  88. package/mongodb.d.ts +137 -68
  89. package/package.json +30 -30
  90. package/src/bson.ts +3 -1
  91. package/src/bulk/common.ts +1 -1
  92. package/src/change_stream.ts +16 -8
  93. package/src/cmap/auth/auth_provider.ts +29 -16
  94. package/src/cmap/auth/gssapi.ts +102 -149
  95. package/src/cmap/auth/mongo_credentials.ts +47 -1
  96. package/src/cmap/auth/mongocr.ts +31 -36
  97. package/src/cmap/auth/mongodb_aws.ts +167 -189
  98. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +26 -0
  99. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +259 -0
  100. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +47 -0
  101. package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +166 -0
  102. package/src/cmap/auth/mongodb_oidc/workflow.ts +21 -0
  103. package/src/cmap/auth/mongodb_oidc.ts +123 -0
  104. package/src/cmap/auth/plain.ts +6 -6
  105. package/src/cmap/auth/providers.ts +5 -2
  106. package/src/cmap/auth/scram.ts +56 -90
  107. package/src/cmap/auth/x509.ts +12 -18
  108. package/src/cmap/command_monitoring_events.ts +5 -2
  109. package/src/cmap/commands.ts +11 -1
  110. package/src/cmap/connect.ts +90 -114
  111. package/src/cmap/connection.ts +92 -90
  112. package/src/cmap/connection_pool.ts +77 -16
  113. package/src/cmap/message_stream.ts +0 -2
  114. package/src/cmap/wire_protocol/compression.ts +1 -1
  115. package/src/cmap/wire_protocol/shared.ts +1 -23
  116. package/src/collection.ts +11 -11
  117. package/src/connection_string.ts +52 -35
  118. package/src/cursor/abstract_cursor.ts +13 -6
  119. package/src/cursor/change_stream_cursor.ts +5 -5
  120. package/src/cursor/find_cursor.ts +1 -1
  121. package/src/db.ts +3 -2
  122. package/src/deps.ts +56 -38
  123. package/src/error.ts +3 -2
  124. package/src/index.ts +7 -0
  125. package/src/mongo_client.ts +35 -10
  126. package/src/mongo_logger.ts +20 -2
  127. package/src/mongo_types.ts +4 -3
  128. package/src/operations/aggregate.ts +4 -2
  129. package/src/operations/create_collection.ts +2 -1
  130. package/src/operations/execute_operation.ts +8 -25
  131. package/src/operations/find.ts +13 -4
  132. package/src/operations/find_and_modify.ts +4 -4
  133. package/src/operations/indexes.ts +12 -4
  134. package/src/operations/list_collections.ts +11 -3
  135. package/src/operations/set_profiling_level.ts +1 -1
  136. package/src/operations/stats.ts +1 -1
  137. package/src/read_concern.ts +2 -2
  138. package/src/read_preference.ts +3 -3
  139. package/src/sdam/common.ts +2 -2
  140. package/src/sdam/monitor.ts +1 -0
  141. package/src/sdam/server.ts +4 -1
  142. package/src/sdam/topology.ts +4 -33
  143. package/src/sessions.ts +2 -1
  144. package/src/transactions.ts +1 -1
  145. package/src/utils.ts +24 -98
  146. package/src/write_concern.ts +1 -1
@@ -1,7 +1,12 @@
1
1
  // Resolves the default auth mechanism according to
2
2
  import type { Document } from '../../bson';
3
- import { MongoAPIError, MongoMissingCredentialsError } from '../../error';
3
+ import {
4
+ MongoAPIError,
5
+ MongoInvalidArgumentError,
6
+ MongoMissingCredentialsError
7
+ } from '../../error';
4
8
  import { GSSAPICanonicalizationValue } from './gssapi';
9
+ import type { OIDCRefreshFunction, OIDCRequestFunction } from './mongodb_oidc';
5
10
  import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './providers';
6
11
 
7
12
  // https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst
@@ -32,6 +37,12 @@ export interface AuthMechanismProperties extends Document {
32
37
  SERVICE_REALM?: string;
33
38
  CANONICALIZE_HOST_NAME?: GSSAPICanonicalizationValue;
34
39
  AWS_SESSION_TOKEN?: string;
40
+ /** @experimental */
41
+ REQUEST_TOKEN_CALLBACK?: OIDCRequestFunction;
42
+ /** @experimental */
43
+ REFRESH_TOKEN_CALLBACK?: OIDCRefreshFunction;
44
+ /** @experimental */
45
+ PROVIDER_NAME?: 'aws';
35
46
  }
36
47
 
37
48
  /** @public */
@@ -137,6 +148,41 @@ export class MongoCredentials {
137
148
  throw new MongoMissingCredentialsError(`Username required for mechanism '${this.mechanism}'`);
138
149
  }
139
150
 
151
+ if (this.mechanism === AuthMechanism.MONGODB_OIDC) {
152
+ if (this.username && this.mechanismProperties.PROVIDER_NAME) {
153
+ throw new MongoInvalidArgumentError(
154
+ `username and PROVIDER_NAME may not be used together for mechanism '${this.mechanism}'.`
155
+ );
156
+ }
157
+
158
+ if (
159
+ this.mechanismProperties.PROVIDER_NAME &&
160
+ this.mechanismProperties.PROVIDER_NAME !== 'aws'
161
+ ) {
162
+ throw new MongoInvalidArgumentError(
163
+ `Currently only a PROVIDER_NAME of 'aws' is supported for mechanism '${this.mechanism}'.`
164
+ );
165
+ }
166
+
167
+ if (
168
+ this.mechanismProperties.REFRESH_TOKEN_CALLBACK &&
169
+ !this.mechanismProperties.REQUEST_TOKEN_CALLBACK
170
+ ) {
171
+ throw new MongoInvalidArgumentError(
172
+ `A REQUEST_TOKEN_CALLBACK must be provided when using a REFRESH_TOKEN_CALLBACK for mechanism '${this.mechanism}'`
173
+ );
174
+ }
175
+
176
+ if (
177
+ !this.mechanismProperties.PROVIDER_NAME &&
178
+ !this.mechanismProperties.REQUEST_TOKEN_CALLBACK
179
+ ) {
180
+ throw new MongoInvalidArgumentError(
181
+ `Either a PROVIDER_NAME or a REQUEST_TOKEN_CALLBACK must be specified for mechanism '${this.mechanism}'.`
182
+ );
183
+ }
184
+ }
185
+
140
186
  if (AUTH_MECHS_AUTH_SRC_EXTERNAL.has(this.mechanism)) {
141
187
  if (this.source != null && this.source !== '$external') {
142
188
  // TODO(NODE-3485): Replace this with a MongoAuthValidationError
@@ -1,47 +1,42 @@
1
1
  import * as crypto from 'crypto';
2
2
 
3
3
  import { MongoMissingCredentialsError } from '../../error';
4
- import { Callback, ns } from '../../utils';
4
+ import { ns } from '../../utils';
5
5
  import { AuthContext, AuthProvider } from './auth_provider';
6
6
 
7
7
  export class MongoCR extends AuthProvider {
8
- override auth(authContext: AuthContext, callback: Callback): void {
8
+ override async auth(authContext: AuthContext): Promise<void> {
9
9
  const { connection, credentials } = authContext;
10
10
  if (!credentials) {
11
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
11
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
12
12
  }
13
- const username = credentials.username;
14
- const password = credentials.password;
15
- const source = credentials.source;
16
- connection.command(ns(`${source}.$cmd`), { getnonce: 1 }, undefined, (err, r) => {
17
- let nonce = null;
18
- let key = null;
19
-
20
- // Get nonce
21
- if (err == null) {
22
- nonce = r.nonce;
23
-
24
- // Use node md5 generator
25
- let md5 = crypto.createHash('md5');
26
-
27
- // Generate keys used for authentication
28
- md5.update(`${username}:mongo:${password}`, 'utf8');
29
- const hash_password = md5.digest('hex');
30
-
31
- // Final key
32
- md5 = crypto.createHash('md5');
33
- md5.update(nonce + username + hash_password, 'utf8');
34
- key = md5.digest('hex');
35
- }
36
-
37
- const authenticateCommand = {
38
- authenticate: 1,
39
- user: username,
40
- nonce,
41
- key
42
- };
43
-
44
- connection.command(ns(`${source}.$cmd`), authenticateCommand, undefined, callback);
45
- });
13
+
14
+ const { username, password, source } = credentials;
15
+
16
+ const { nonce } = await connection.commandAsync(
17
+ ns(`${source}.$cmd`),
18
+ { getnonce: 1 },
19
+ undefined
20
+ );
21
+
22
+ const hashPassword = crypto
23
+ .createHash('md5')
24
+ .update(`${username}:mongo:${password}`, 'utf8')
25
+ .digest('hex');
26
+
27
+ // Final key
28
+ const key = crypto
29
+ .createHash('md5')
30
+ .update(`${nonce}${username}${hashPassword}`, 'utf8')
31
+ .digest('hex');
32
+
33
+ const authenticateCommand = {
34
+ authenticate: 1,
35
+ user: username,
36
+ nonce,
37
+ key
38
+ };
39
+
40
+ await connection.commandAsync(ns(`${source}.$cmd`), authenticateCommand, undefined);
46
41
  }
47
42
  }
@@ -1,6 +1,7 @@
1
1
  import * as crypto from 'crypto';
2
2
  import * as http from 'http';
3
3
  import * as url from 'url';
4
+ import { promisify } from 'util';
4
5
 
5
6
  import type { Binary, BSONSerializeOptions } from '../../bson';
6
7
  import * as BSON from '../../bson';
@@ -11,7 +12,7 @@ import {
11
12
  MongoMissingCredentialsError,
12
13
  MongoRuntimeError
13
14
  } from '../../error';
14
- import { ByteUtils, Callback, maxWireVersion, ns } from '../../utils';
15
+ import { ByteUtils, maxWireVersion, ns } from '../../utils';
15
16
  import { AuthContext, AuthProvider } from './auth_provider';
16
17
  import { MongoCredentials } from './mongo_credentials';
17
18
  import { AuthMechanism } from './providers';
@@ -21,6 +22,7 @@ const AWS_RELATIVE_URI = 'http://169.254.170.2';
21
22
  const AWS_EC2_URI = 'http://169.254.169.254';
22
23
  const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
23
24
  const bsonOptions: BSONSerializeOptions = {
25
+ useBigInt64: false,
24
26
  promoteLongs: true,
25
27
  promoteValues: true,
26
28
  promoteBuffers: false,
@@ -34,37 +36,36 @@ interface AWSSaslContinuePayload {
34
36
  }
35
37
 
36
38
  export class MongoDBAWS extends AuthProvider {
37
- override auth(authContext: AuthContext, callback: Callback): void {
38
- const { connection, credentials } = authContext;
39
- if (!credentials) {
40
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
39
+ randomBytesAsync: (size: number) => Promise<Buffer>;
40
+
41
+ constructor() {
42
+ super();
43
+ this.randomBytesAsync = promisify(crypto.randomBytes);
44
+ }
45
+
46
+ override async auth(authContext: AuthContext): Promise<void> {
47
+ const { connection } = authContext;
48
+ if (!authContext.credentials) {
49
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
41
50
  }
42
51
 
43
52
  if ('kModuleError' in aws4) {
44
- return callback(aws4['kModuleError']);
53
+ throw aws4['kModuleError'];
45
54
  }
46
55
  const { sign } = aws4;
47
56
 
48
57
  if (maxWireVersion(connection) < 9) {
49
- callback(
50
- new MongoCompatibilityError(
51
- 'MONGODB-AWS authentication requires MongoDB version 4.4 or later'
52
- )
58
+ throw new MongoCompatibilityError(
59
+ 'MONGODB-AWS authentication requires MongoDB version 4.4 or later'
53
60
  );
54
- return;
55
61
  }
56
62
 
57
- if (!credentials.username) {
58
- makeTempCredentials(credentials, (err, tempCredentials) => {
59
- if (err || !tempCredentials) return callback(err);
60
-
61
- authContext.credentials = tempCredentials;
62
- this.auth(authContext, callback);
63
- });
64
-
65
- return;
63
+ if (!authContext.credentials.username) {
64
+ authContext.credentials = await makeTempCredentials(authContext.credentials);
66
65
  }
67
66
 
67
+ const { credentials } = authContext;
68
+
68
69
  const accessKeyId = credentials.username;
69
70
  const secretAccessKey = credentials.password;
70
71
  const sessionToken = credentials.mechanismProperties.AWS_SESSION_TOKEN;
@@ -78,87 +79,75 @@ export class MongoDBAWS extends AuthProvider {
78
79
  : undefined;
79
80
 
80
81
  const db = credentials.source;
81
- crypto.randomBytes(32, (err, nonce) => {
82
- if (err) {
83
- callback(err);
84
- return;
85
- }
82
+ const nonce = await this.randomBytesAsync(32);
83
+
84
+ const saslStart = {
85
+ saslStart: 1,
86
+ mechanism: 'MONGODB-AWS',
87
+ payload: BSON.serialize({ r: nonce, p: ASCII_N }, bsonOptions)
88
+ };
89
+
90
+ const saslStartResponse = await connection.commandAsync(ns(`${db}.$cmd`), saslStart, undefined);
91
+
92
+ const serverResponse = BSON.deserialize(saslStartResponse.payload.buffer, bsonOptions) as {
93
+ s: Binary;
94
+ h: string;
95
+ };
96
+ const host = serverResponse.h;
97
+ const serverNonce = serverResponse.s.buffer;
98
+ if (serverNonce.length !== 64) {
99
+ // TODO(NODE-3483)
100
+ throw new MongoRuntimeError(`Invalid server nonce length ${serverNonce.length}, expected 64`);
101
+ }
86
102
 
87
- const saslStart = {
88
- saslStart: 1,
89
- mechanism: 'MONGODB-AWS',
90
- payload: BSON.serialize({ r: nonce, p: ASCII_N }, bsonOptions)
91
- };
92
-
93
- connection.command(ns(`${db}.$cmd`), saslStart, undefined, (err, res) => {
94
- if (err) return callback(err);
95
-
96
- const serverResponse = BSON.deserialize(res.payload.buffer, bsonOptions) as {
97
- s: Binary;
98
- h: string;
99
- };
100
- const host = serverResponse.h;
101
- const serverNonce = serverResponse.s.buffer;
102
- if (serverNonce.length !== 64) {
103
- callback(
104
- // TODO(NODE-3483)
105
- new MongoRuntimeError(`Invalid server nonce length ${serverNonce.length}, expected 64`)
106
- );
103
+ if (!ByteUtils.equals(serverNonce.subarray(0, nonce.byteLength), nonce)) {
104
+ // throw because the serverNonce's leading 32 bytes must equal the client nonce's 32 bytes
105
+ // https://github.com/mongodb/specifications/blob/875446db44aade414011731840831f38a6c668df/source/auth/auth.rst#id11
107
106
 
108
- return;
109
- }
107
+ // TODO(NODE-3483)
108
+ throw new MongoRuntimeError('Server nonce does not begin with client nonce');
109
+ }
110
110
 
111
- if (!ByteUtils.equals(serverNonce.subarray(0, nonce.byteLength), nonce)) {
112
- // throw because the serverNonce's leading 32 bytes must equal the client nonce's 32 bytes
113
- // https://github.com/mongodb/specifications/blob/875446db44aade414011731840831f38a6c668df/source/auth/auth.rst#id11
111
+ if (host.length < 1 || host.length > 255 || host.indexOf('..') !== -1) {
112
+ // TODO(NODE-3483)
113
+ throw new MongoRuntimeError(`Server returned an invalid host: "${host}"`);
114
+ }
114
115
 
115
- // TODO(NODE-3483)
116
- callback(new MongoRuntimeError('Server nonce does not begin with client nonce'));
117
- return;
118
- }
116
+ const body = 'Action=GetCallerIdentity&Version=2011-06-15';
117
+ const options = sign(
118
+ {
119
+ method: 'POST',
120
+ host,
121
+ region: deriveRegion(serverResponse.h),
122
+ service: 'sts',
123
+ headers: {
124
+ 'Content-Type': 'application/x-www-form-urlencoded',
125
+ 'Content-Length': body.length,
126
+ 'X-MongoDB-Server-Nonce': ByteUtils.toBase64(serverNonce),
127
+ 'X-MongoDB-GS2-CB-Flag': 'n'
128
+ },
129
+ path: '/',
130
+ body
131
+ },
132
+ awsCredentials
133
+ );
119
134
 
120
- if (host.length < 1 || host.length > 255 || host.indexOf('..') !== -1) {
121
- // TODO(NODE-3483)
122
- callback(new MongoRuntimeError(`Server returned an invalid host: "${host}"`));
123
- return;
124
- }
135
+ const payload: AWSSaslContinuePayload = {
136
+ a: options.headers.Authorization,
137
+ d: options.headers['X-Amz-Date']
138
+ };
125
139
 
126
- const body = 'Action=GetCallerIdentity&Version=2011-06-15';
127
- const options = sign(
128
- {
129
- method: 'POST',
130
- host,
131
- region: deriveRegion(serverResponse.h),
132
- service: 'sts',
133
- headers: {
134
- 'Content-Type': 'application/x-www-form-urlencoded',
135
- 'Content-Length': body.length,
136
- 'X-MongoDB-Server-Nonce': ByteUtils.toBase64(serverNonce),
137
- 'X-MongoDB-GS2-CB-Flag': 'n'
138
- },
139
- path: '/',
140
- body
141
- },
142
- awsCredentials
143
- );
144
-
145
- const payload: AWSSaslContinuePayload = {
146
- a: options.headers.Authorization,
147
- d: options.headers['X-Amz-Date']
148
- };
149
- if (sessionToken) {
150
- payload.t = sessionToken;
151
- }
140
+ if (sessionToken) {
141
+ payload.t = sessionToken;
142
+ }
152
143
 
153
- const saslContinue = {
154
- saslContinue: 1,
155
- conversationId: 1,
156
- payload: BSON.serialize(payload, bsonOptions)
157
- };
144
+ const saslContinue = {
145
+ saslContinue: 1,
146
+ conversationId: 1,
147
+ payload: BSON.serialize(payload, bsonOptions)
148
+ };
158
149
 
159
- connection.command(ns(`${db}.$cmd`), saslContinue, undefined, callback);
160
- });
161
- });
150
+ await connection.commandAsync(ns(`${db}.$cmd`), saslContinue, undefined);
162
151
  }
163
152
  }
164
153
 
@@ -178,27 +167,21 @@ export interface AWSCredentials {
178
167
  expiration?: Date;
179
168
  }
180
169
 
181
- function makeTempCredentials(credentials: MongoCredentials, callback: Callback<MongoCredentials>) {
182
- function done(creds: AWSTempCredentials) {
170
+ async function makeTempCredentials(credentials: MongoCredentials): Promise<MongoCredentials> {
171
+ function makeMongoCredentialsFromAWSTemp(creds: AWSTempCredentials) {
183
172
  if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) {
184
- callback(
185
- new MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials')
186
- );
187
- return;
173
+ throw new MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials');
188
174
  }
189
175
 
190
- callback(
191
- undefined,
192
- new MongoCredentials({
193
- username: creds.AccessKeyId,
194
- password: creds.SecretAccessKey,
195
- source: credentials.source,
196
- mechanism: AuthMechanism.MONGODB_AWS,
197
- mechanismProperties: {
198
- AWS_SESSION_TOKEN: creds.Token
199
- }
200
- })
201
- );
176
+ return new MongoCredentials({
177
+ username: creds.AccessKeyId,
178
+ password: creds.SecretAccessKey,
179
+ source: credentials.source,
180
+ mechanism: AuthMechanism.MONGODB_AWS,
181
+ mechanismProperties: {
182
+ AWS_SESSION_TOKEN: creds.Token
183
+ }
184
+ });
202
185
  }
203
186
 
204
187
  const credentialProvider = getAwsCredentialProvider();
@@ -209,47 +192,32 @@ function makeTempCredentials(credentials: MongoCredentials, callback: Callback<M
209
192
  // If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
210
193
  // is set then drivers MUST assume that it was set by an AWS ECS agent
211
194
  if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
212
- request(
213
- `${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`,
214
- undefined,
215
- (err, res) => {
216
- if (err) return callback(err);
217
- done(res);
218
- }
195
+ return makeMongoCredentialsFromAWSTemp(
196
+ await request(`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`)
219
197
  );
220
-
221
- return;
222
198
  }
223
199
 
224
200
  // Otherwise assume we are on an EC2 instance
225
201
 
226
202
  // get a token
227
- request(
228
- `${AWS_EC2_URI}/latest/api/token`,
229
- { method: 'PUT', json: false, headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 } },
230
- (err, token) => {
231
- if (err) return callback(err);
232
-
233
- // get role name
234
- request(
235
- `${AWS_EC2_URI}/${AWS_EC2_PATH}`,
236
- { json: false, headers: { 'X-aws-ec2-metadata-token': token } },
237
- (err, roleName) => {
238
- if (err) return callback(err);
239
-
240
- // get temp credentials
241
- request(
242
- `${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`,
243
- { headers: { 'X-aws-ec2-metadata-token': token } },
244
- (err, creds) => {
245
- if (err) return callback(err);
246
- done(creds);
247
- }
248
- );
249
- }
250
- );
251
- }
252
- );
203
+ const token = await request(`${AWS_EC2_URI}/latest/api/token`, {
204
+ method: 'PUT',
205
+ json: false,
206
+ headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 }
207
+ });
208
+
209
+ // get role name
210
+ const roleName = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, {
211
+ json: false,
212
+ headers: { 'X-aws-ec2-metadata-token': token }
213
+ });
214
+
215
+ // get temp credentials
216
+ const creds = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, {
217
+ headers: { 'X-aws-ec2-metadata-token': token }
218
+ });
219
+
220
+ return makeMongoCredentialsFromAWSTemp(creds);
253
221
  } else {
254
222
  /*
255
223
  * Creates a credential provider that will attempt to find credentials from the
@@ -263,18 +231,17 @@ function makeTempCredentials(credentials: MongoCredentials, callback: Callback<M
263
231
  */
264
232
  const { fromNodeProviderChain } = credentialProvider;
265
233
  const provider = fromNodeProviderChain();
266
- provider()
267
- .then((creds: AWSCredentials) => {
268
- done({
269
- AccessKeyId: creds.accessKeyId,
270
- SecretAccessKey: creds.secretAccessKey,
271
- Token: creds.sessionToken,
272
- Expiration: creds.expiration
273
- });
274
- })
275
- .catch((error: Error) => {
276
- callback(new MongoAWSError(error.message));
234
+ try {
235
+ const creds = await provider();
236
+ return makeMongoCredentialsFromAWSTemp({
237
+ AccessKeyId: creds.accessKeyId,
238
+ SecretAccessKey: creds.secretAccessKey,
239
+ Token: creds.sessionToken,
240
+ Expiration: creds.expiration
277
241
  });
242
+ } catch (error) {
243
+ throw new MongoAWSError(error.message);
244
+ }
278
245
  }
279
246
  }
280
247
 
@@ -294,42 +261,53 @@ interface RequestOptions {
294
261
  headers?: http.OutgoingHttpHeaders;
295
262
  }
296
263
 
297
- function request(uri: string, _options: RequestOptions | undefined, callback: Callback) {
298
- const options = Object.assign(
299
- {
264
+ async function request(uri: string): Promise<Record<string, any>>;
265
+ async function request(
266
+ uri: string,
267
+ options?: { json?: true } & RequestOptions
268
+ ): Promise<Record<string, any>>;
269
+ async function request(uri: string, options?: { json: false } & RequestOptions): Promise<string>;
270
+ async function request(
271
+ uri: string,
272
+ options: RequestOptions = {}
273
+ ): Promise<string | Record<string, any>> {
274
+ return new Promise<string | Record<string, any>>((resolve, reject) => {
275
+ const requestOptions = {
300
276
  method: 'GET',
301
277
  timeout: 10000,
302
- json: true
303
- },
304
- url.parse(uri),
305
- _options
306
- );
307
-
308
- const req = http.request(options, res => {
309
- res.setEncoding('utf8');
310
-
311
- let data = '';
312
- res.on('data', d => (data += d));
313
- res.on('end', () => {
314
- if (options.json === false) {
315
- callback(undefined, data);
316
- return;
317
- }
278
+ json: true,
279
+ ...url.parse(uri),
280
+ ...options
281
+ };
318
282
 
319
- try {
320
- const parsed = JSON.parse(data);
321
- callback(undefined, parsed);
322
- } catch (err) {
323
- // TODO(NODE-3483)
324
- callback(new MongoRuntimeError(`Invalid JSON response: "${data}"`));
325
- }
283
+ const req = http.request(requestOptions, res => {
284
+ res.setEncoding('utf8');
285
+
286
+ let data = '';
287
+ res.on('data', d => {
288
+ data += d;
289
+ });
290
+
291
+ res.once('end', () => {
292
+ if (options.json === false) {
293
+ resolve(data);
294
+ return;
295
+ }
296
+
297
+ try {
298
+ const parsed = JSON.parse(data);
299
+ resolve(parsed);
300
+ } catch {
301
+ // TODO(NODE-3483)
302
+ reject(new MongoRuntimeError(`Invalid JSON response: "${data}"`));
303
+ }
304
+ });
326
305
  });
327
- });
328
306
 
329
- req.on('timeout', () => {
330
- req.destroy(new MongoAWSError(`AWS request to ${uri} timed out after ${options.timeout} ms`));
307
+ req.once('timeout', () =>
308
+ req.destroy(new MongoAWSError(`AWS request to ${uri} timed out after ${options.timeout} ms`))
309
+ );
310
+ req.once('error', error => reject(error));
311
+ req.end();
331
312
  });
332
-
333
- req.on('error', err => callback(err));
334
- req.end();
335
313
  }
@@ -0,0 +1,26 @@
1
+ import { readFile } from 'fs/promises';
2
+
3
+ import { MongoAWSError } from '../../../error';
4
+ import { ServiceWorkflow } from './service_workflow';
5
+
6
+ /**
7
+ * Device workflow implementation for AWS.
8
+ *
9
+ * @internal
10
+ */
11
+ export class AwsServiceWorkflow extends ServiceWorkflow {
12
+ constructor() {
13
+ super();
14
+ }
15
+
16
+ /**
17
+ * Get the token from the environment.
18
+ */
19
+ async getToken(): Promise<string> {
20
+ const tokenFile = process.env.AWS_WEB_IDENTITY_TOKEN_FILE;
21
+ if (!tokenFile) {
22
+ throw new MongoAWSError('AWS_WEB_IDENTITY_TOKEN_FILE must be set in the environment.');
23
+ }
24
+ return readFile(tokenFile, 'utf8');
25
+ }
26
+ }