mongodb 5.1.0 → 5.3.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 (144) hide show
  1. package/README.md +25 -22
  2. package/lib/admin.js +2 -0
  3. package/lib/admin.js.map +1 -1
  4. package/lib/bulk/common.js +28 -7
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/change_stream.js +1 -1
  7. package/lib/cmap/auth/auth_provider.js +21 -10
  8. package/lib/cmap/auth/auth_provider.js.map +1 -1
  9. package/lib/cmap/auth/gssapi.js +71 -116
  10. package/lib/cmap/auth/gssapi.js.map +1 -1
  11. package/lib/cmap/auth/mongo_credentials.js +7 -9
  12. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  13. package/lib/cmap/auth/mongocr.js +20 -29
  14. package/lib/cmap/auth/mongocr.js.map +1 -1
  15. package/lib/cmap/auth/mongodb_aws.js +125 -140
  16. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +28 -0
  18. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -0
  19. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +178 -0
  20. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -0
  21. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +41 -0
  22. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -0
  23. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +115 -0
  24. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -0
  25. package/lib/cmap/auth/mongodb_oidc/workflow.js +3 -0
  26. package/lib/cmap/auth/mongodb_oidc/workflow.js.map +1 -0
  27. package/lib/cmap/auth/mongodb_oidc.js +59 -0
  28. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  29. package/lib/cmap/auth/plain.js +4 -5
  30. package/lib/cmap/auth/plain.js.map +1 -1
  31. package/lib/cmap/auth/providers.js +1 -1
  32. package/lib/cmap/auth/providers.js.map +1 -1
  33. package/lib/cmap/auth/scram.js +45 -73
  34. package/lib/cmap/auth/scram.js.map +1 -1
  35. package/lib/cmap/auth/x509.js +8 -11
  36. package/lib/cmap/auth/x509.js.map +1 -1
  37. package/lib/cmap/command_monitoring_events.js +14 -5
  38. package/lib/cmap/command_monitoring_events.js.map +1 -1
  39. package/lib/cmap/commands.js +1 -1
  40. package/lib/cmap/commands.js.map +1 -1
  41. package/lib/cmap/connect.js +73 -86
  42. package/lib/cmap/connect.js.map +1 -1
  43. package/lib/cmap/connection.js +19 -23
  44. package/lib/cmap/connection.js.map +1 -1
  45. package/lib/cmap/connection_pool.js +56 -14
  46. package/lib/cmap/connection_pool.js.map +1 -1
  47. package/lib/cmap/connection_pool_events.js +28 -3
  48. package/lib/cmap/connection_pool_events.js.map +1 -1
  49. package/lib/cmap/handshake/client_metadata.js +173 -0
  50. package/lib/cmap/handshake/client_metadata.js.map +1 -0
  51. package/lib/cmap/message_stream.js.map +1 -1
  52. package/lib/cmap/wire_protocol/shared.js +1 -16
  53. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  54. package/lib/collection.js +10 -10
  55. package/lib/connection_string.js +50 -69
  56. package/lib/connection_string.js.map +1 -1
  57. package/lib/constants.js +11 -0
  58. package/lib/constants.js.map +1 -1
  59. package/lib/cursor/abstract_cursor.js +2 -1
  60. package/lib/cursor/abstract_cursor.js.map +1 -1
  61. package/lib/cursor/find_cursor.js +1 -1
  62. package/lib/db.js +4 -2
  63. package/lib/db.js.map +1 -1
  64. package/lib/error.js +2 -1
  65. package/lib/error.js.map +1 -1
  66. package/lib/mongo_client.js +23 -2
  67. package/lib/mongo_client.js.map +1 -1
  68. package/lib/mongo_logger.js +236 -23
  69. package/lib/mongo_logger.js.map +1 -1
  70. package/lib/operations/add_user.js.map +1 -1
  71. package/lib/operations/execute_operation.js +8 -27
  72. package/lib/operations/execute_operation.js.map +1 -1
  73. package/lib/operations/find.js +1 -8
  74. package/lib/operations/find.js.map +1 -1
  75. package/lib/operations/update.js.map +1 -1
  76. package/lib/read_concern.js +1 -1
  77. package/lib/read_preference.js +2 -2
  78. package/lib/sdam/srv_polling.js +1 -15
  79. package/lib/sdam/srv_polling.js.map +1 -1
  80. package/lib/sdam/topology.js +0 -16
  81. package/lib/sdam/topology.js.map +1 -1
  82. package/lib/utils.js +33 -90
  83. package/lib/utils.js.map +1 -1
  84. package/lib/write_concern.js +1 -1
  85. package/mongodb.d.ts +242 -93
  86. package/package.json +30 -31
  87. package/src/admin.ts +2 -0
  88. package/src/bulk/common.ts +29 -8
  89. package/src/change_stream.ts +5 -5
  90. package/src/cmap/auth/auth_provider.ts +29 -16
  91. package/src/cmap/auth/gssapi.ts +102 -149
  92. package/src/cmap/auth/mongo_credentials.ts +14 -23
  93. package/src/cmap/auth/mongocr.ts +31 -36
  94. package/src/cmap/auth/mongodb_aws.ts +166 -189
  95. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +26 -0
  96. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +259 -0
  97. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +47 -0
  98. package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +166 -0
  99. package/src/cmap/auth/mongodb_oidc/workflow.ts +21 -0
  100. package/src/cmap/auth/mongodb_oidc.ts +101 -17
  101. package/src/cmap/auth/plain.ts +6 -6
  102. package/src/cmap/auth/providers.ts +2 -2
  103. package/src/cmap/auth/scram.ts +56 -90
  104. package/src/cmap/auth/x509.ts +12 -18
  105. package/src/cmap/command_monitoring_events.ts +18 -3
  106. package/src/cmap/commands.ts +1 -1
  107. package/src/cmap/connect.ts +92 -114
  108. package/src/cmap/connection.ts +39 -25
  109. package/src/cmap/connection_pool.ts +89 -18
  110. package/src/cmap/connection_pool_events.ts +68 -6
  111. package/src/cmap/handshake/client_metadata.ts +272 -0
  112. package/src/cmap/message_stream.ts +0 -2
  113. package/src/cmap/wire_protocol/compression.ts +1 -1
  114. package/src/cmap/wire_protocol/shared.ts +1 -23
  115. package/src/collection.ts +13 -13
  116. package/src/connection_string.ts +56 -72
  117. package/src/constants.ts +11 -0
  118. package/src/cursor/abstract_cursor.ts +3 -2
  119. package/src/cursor/change_stream_cursor.ts +5 -5
  120. package/src/cursor/find_cursor.ts +1 -1
  121. package/src/db.ts +4 -2
  122. package/src/deps.ts +3 -2
  123. package/src/error.ts +3 -2
  124. package/src/index.ts +21 -3
  125. package/src/mongo_client.ts +60 -14
  126. package/src/mongo_logger.ts +341 -40
  127. package/src/mongo_types.ts +2 -2
  128. package/src/operations/add_user.ts +8 -2
  129. package/src/operations/aggregate.ts +1 -1
  130. package/src/operations/create_collection.ts +1 -1
  131. package/src/operations/execute_operation.ts +8 -25
  132. package/src/operations/find.ts +1 -11
  133. package/src/operations/find_and_modify.ts +4 -4
  134. package/src/operations/set_profiling_level.ts +1 -1
  135. package/src/operations/stats.ts +1 -1
  136. package/src/operations/update.ts +8 -4
  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/srv_polling.ts +1 -16
  141. package/src/sdam/topology.ts +1 -23
  142. package/src/transactions.ts +1 -1
  143. package/src/utils.ts +37 -147
  144. package/src/write_concern.ts +1 -1
@@ -0,0 +1,259 @@
1
+ import { Binary, BSON, type Document } from 'bson';
2
+
3
+ import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../../error';
4
+ import { ns } from '../../../utils';
5
+ import type { Connection } from '../../connection';
6
+ import type { MongoCredentials } from '../mongo_credentials';
7
+ import type { OIDCMechanismServerStep1, OIDCRequestTokenResult } from '../mongodb_oidc';
8
+ import { AuthMechanism } from '../providers';
9
+ import { TokenEntryCache } from './token_entry_cache';
10
+ import type { Workflow } from './workflow';
11
+
12
+ /* 5 minutes in milliseconds */
13
+ const TIMEOUT_MS = 300000;
14
+
15
+ /**
16
+ * OIDC implementation of a callback based workflow.
17
+ * @internal
18
+ */
19
+ export class CallbackWorkflow implements Workflow {
20
+ cache: TokenEntryCache;
21
+
22
+ /**
23
+ * Instantiate the workflow
24
+ */
25
+ constructor() {
26
+ this.cache = new TokenEntryCache();
27
+ }
28
+
29
+ /**
30
+ * Get the document to add for speculative authentication. Is empty when
31
+ * callbacks are in play.
32
+ */
33
+ speculativeAuth(): Promise<Document> {
34
+ return Promise.resolve({});
35
+ }
36
+
37
+ /**
38
+ * Execute the workflow.
39
+ *
40
+ * Steps:
41
+ * - If an entry is in the cache
42
+ * - If it is not expired
43
+ * - Skip step one and use the entry to execute step two.
44
+ * - If it is expired
45
+ * - If the refresh callback exists
46
+ * - remove expired entry from cache
47
+ * - call the refresh callback.
48
+ * - put the new entry in the cache.
49
+ * - execute step two.
50
+ * - If the refresh callback does not exist.
51
+ * - remove expired entry from cache
52
+ * - call the request callback.
53
+ * - put the new entry in the cache.
54
+ * - execute step two.
55
+ * - If no entry is in the cache.
56
+ * - execute step one.
57
+ * - call the refresh callback.
58
+ * - put the new entry in the cache.
59
+ * - execute step two.
60
+ */
61
+ async execute(
62
+ connection: Connection,
63
+ credentials: MongoCredentials,
64
+ reauthenticate = false
65
+ ): Promise<Document> {
66
+ const request = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
67
+ const refresh = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
68
+
69
+ const entry = this.cache.getEntry(
70
+ connection.address,
71
+ credentials.username,
72
+ request || null,
73
+ refresh || null
74
+ );
75
+ if (entry) {
76
+ // Check if the entry is not expired and if we are reauthenticating.
77
+ if (!reauthenticate && entry.isValid()) {
78
+ // Skip step one and execute the step two saslContinue.
79
+ try {
80
+ const result = await finishAuth(entry.tokenResult, undefined, connection, credentials);
81
+ return result;
82
+ } catch (error) {
83
+ // If authentication errors when using a cached token we remove it from
84
+ // the cache.
85
+ this.cache.deleteEntry(
86
+ connection.address,
87
+ credentials.username || '',
88
+ request || null,
89
+ refresh || null
90
+ );
91
+ throw error;
92
+ }
93
+ } else {
94
+ // Remove the expired entry from the cache.
95
+ this.cache.deleteEntry(
96
+ connection.address,
97
+ credentials.username || '',
98
+ request || null,
99
+ refresh || null
100
+ );
101
+ // Execute a refresh of the token and finish auth.
102
+ return this.refreshAndFinish(
103
+ connection,
104
+ credentials,
105
+ entry.serverResult,
106
+ entry.tokenResult
107
+ );
108
+ }
109
+ } else {
110
+ // No entry means to start with the step one saslStart.
111
+ const result = await connection.commandAsync(
112
+ ns(credentials.source),
113
+ startCommandDocument(credentials),
114
+ undefined
115
+ );
116
+ const stepOne = BSON.deserialize(result.payload.buffer) as OIDCMechanismServerStep1;
117
+ // Call the request callback and finish auth.
118
+ return this.requestAndFinish(connection, credentials, stepOne, result.conversationId);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Execute the refresh callback if it exists, otherwise the request callback, then
124
+ * finish the authentication.
125
+ */
126
+ private async refreshAndFinish(
127
+ connection: Connection,
128
+ credentials: MongoCredentials,
129
+ stepOneResult: OIDCMechanismServerStep1,
130
+ tokenResult: OIDCRequestTokenResult,
131
+ conversationId?: number
132
+ ): Promise<Document> {
133
+ const request = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
134
+ const refresh = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
135
+ // If a refresh callback exists, use it. Otherwise use the request callback.
136
+ if (refresh) {
137
+ const result: OIDCRequestTokenResult = await refresh(
138
+ credentials.username,
139
+ stepOneResult,
140
+ tokenResult,
141
+ TIMEOUT_MS
142
+ );
143
+ // Validate the result.
144
+ if (!result || !result.accessToken) {
145
+ throw new MongoMissingCredentialsError(
146
+ 'REFRESH_TOKEN_CALLBACK must return a valid object with an accessToken'
147
+ );
148
+ }
149
+ // Cache a new entry and continue with the saslContinue.
150
+ this.cache.addEntry(
151
+ connection.address,
152
+ credentials.username || '',
153
+ request || null,
154
+ refresh,
155
+ result,
156
+ stepOneResult
157
+ );
158
+ return finishAuth(result, conversationId, connection, credentials);
159
+ } else {
160
+ // Fallback to using the request callback.
161
+ return this.requestAndFinish(connection, credentials, stepOneResult, conversationId);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Execute the request callback and finish authentication.
167
+ */
168
+ private async requestAndFinish(
169
+ connection: Connection,
170
+ credentials: MongoCredentials,
171
+ stepOneResult: OIDCMechanismServerStep1,
172
+ conversationId?: number
173
+ ): Promise<Document> {
174
+ // Call the request callback.
175
+ const request = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
176
+ const refresh = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
177
+ // Always clear expired entries from the cache on each finish as cleanup.
178
+ this.cache.deleteExpiredEntries();
179
+ if (!request) {
180
+ // Request callback must be present.
181
+ throw new MongoInvalidArgumentError(
182
+ 'Auth mechanism property REQUEST_TOKEN_CALLBACK is required.'
183
+ );
184
+ }
185
+ const tokenResult = await request(credentials.username, stepOneResult, TIMEOUT_MS);
186
+ // Validate the result.
187
+ if (!tokenResult || !tokenResult.accessToken) {
188
+ throw new MongoMissingCredentialsError(
189
+ 'REQUEST_TOKEN_CALLBACK must return a valid object with an accessToken'
190
+ );
191
+ }
192
+ // Cache a new entry and continue with the saslContinue.
193
+ this.cache.addEntry(
194
+ connection.address,
195
+ credentials.username || '',
196
+ request,
197
+ refresh || null,
198
+ tokenResult,
199
+ stepOneResult
200
+ );
201
+ return finishAuth(tokenResult, conversationId, connection, credentials);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Cache the result of the user supplied callback and execute the
207
+ * step two saslContinue.
208
+ */
209
+ async function finishAuth(
210
+ result: OIDCRequestTokenResult,
211
+ conversationId: number | undefined,
212
+ connection: Connection,
213
+ credentials: MongoCredentials
214
+ ): Promise<Document> {
215
+ // Execute the step two saslContinue.
216
+ return connection.commandAsync(
217
+ ns(credentials.source),
218
+ continueCommandDocument(result.accessToken, conversationId),
219
+ undefined
220
+ );
221
+ }
222
+
223
+ /**
224
+ * Generate the saslStart command document.
225
+ */
226
+ function startCommandDocument(credentials: MongoCredentials): Document {
227
+ const payload: Document = {};
228
+ if (credentials.username) {
229
+ payload.n = credentials.username;
230
+ }
231
+ return {
232
+ saslStart: 1,
233
+ autoAuthorize: 1,
234
+ mechanism: AuthMechanism.MONGODB_OIDC,
235
+ payload: new Binary(BSON.serialize(payload))
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Generate the saslContinue command document.
241
+ */
242
+ function continueCommandDocument(token: string, conversationId?: number): Document {
243
+ if (conversationId) {
244
+ return {
245
+ saslContinue: 1,
246
+ conversationId: conversationId,
247
+ payload: new Binary(BSON.serialize({ jwt: token }))
248
+ };
249
+ }
250
+ // saslContinue requires a conversationId in the command to be valid so in this
251
+ // case the server allows "step two" to actually be a saslStart with the token
252
+ // as the jwt since the use of the cached value has no correlating conversating
253
+ // on the particular connection.
254
+ return {
255
+ saslStart: 1,
256
+ mechanism: AuthMechanism.MONGODB_OIDC,
257
+ payload: new Binary(BSON.serialize({ jwt: token }))
258
+ };
259
+ }
@@ -0,0 +1,47 @@
1
+ import { BSON, type Document } from 'bson';
2
+
3
+ import { ns } from '../../../utils';
4
+ import type { Connection } from '../../connection';
5
+ import type { MongoCredentials } from '../mongo_credentials';
6
+ import { AuthMechanism } from '../providers';
7
+ import type { Workflow } from './workflow';
8
+
9
+ /**
10
+ * Common behaviour for OIDC device workflows.
11
+ * @internal
12
+ */
13
+ export abstract class ServiceWorkflow implements Workflow {
14
+ /**
15
+ * Execute the workflow. Looks for AWS_WEB_IDENTITY_TOKEN_FILE in the environment
16
+ * and then attempts to read the token from that path.
17
+ */
18
+ async execute(connection: Connection, credentials: MongoCredentials): Promise<Document> {
19
+ const token = await this.getToken();
20
+ const command = commandDocument(token);
21
+ return connection.commandAsync(ns(credentials.source), command, undefined);
22
+ }
23
+
24
+ /**
25
+ * Get the document to add for speculative authentication.
26
+ */
27
+ async speculativeAuth(): Promise<Document> {
28
+ const token = await this.getToken();
29
+ return { speculativeAuthenticate: commandDocument(token) };
30
+ }
31
+
32
+ /**
33
+ * Get the token from the environment or endpoint.
34
+ */
35
+ abstract getToken(): Promise<string>;
36
+ }
37
+
38
+ /**
39
+ * Create the saslStart command document.
40
+ */
41
+ export function commandDocument(token: string): Document {
42
+ return {
43
+ saslStart: 1,
44
+ mechanism: AuthMechanism.MONGODB_OIDC,
45
+ payload: BSON.serialize({ jwt: token })
46
+ };
47
+ }
@@ -0,0 +1,166 @@
1
+ import type {
2
+ OIDCMechanismServerStep1,
3
+ OIDCRefreshFunction,
4
+ OIDCRequestFunction,
5
+ OIDCRequestTokenResult
6
+ } from '../mongodb_oidc';
7
+
8
+ /* 5 minutes in milliseonds */
9
+ const EXPIRATION_BUFFER_MS = 300000;
10
+ /* Default expiration is now for when no expiration provided */
11
+ const DEFAULT_EXPIRATION_SECS = 0;
12
+ /* Counter for function "hashes".*/
13
+ let FN_HASH_COUNTER = 0;
14
+ /* No function present function */
15
+ const NO_FUNCTION: OIDCRequestFunction = () => {
16
+ return Promise.resolve({ accessToken: 'test' });
17
+ };
18
+ /* The map of function hashes */
19
+ const FN_HASHES = new WeakMap<OIDCRequestFunction | OIDCRefreshFunction, number>();
20
+ /* Put the no function hash in the map. */
21
+ FN_HASHES.set(NO_FUNCTION, FN_HASH_COUNTER);
22
+
23
+ /** @internal */
24
+ export class TokenEntry {
25
+ tokenResult: OIDCRequestTokenResult;
26
+ serverResult: OIDCMechanismServerStep1;
27
+ expiration: number;
28
+
29
+ /**
30
+ * Instantiate the entry.
31
+ */
32
+ constructor(
33
+ tokenResult: OIDCRequestTokenResult,
34
+ serverResult: OIDCMechanismServerStep1,
35
+ expiration: number
36
+ ) {
37
+ this.tokenResult = tokenResult;
38
+ this.serverResult = serverResult;
39
+ this.expiration = expiration;
40
+ }
41
+
42
+ /**
43
+ * The entry is still valid if the expiration is more than
44
+ * 5 minutes from the expiration time.
45
+ */
46
+ isValid() {
47
+ return this.expiration - Date.now() > EXPIRATION_BUFFER_MS;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Cache of OIDC token entries.
53
+ * @internal
54
+ */
55
+ export class TokenEntryCache {
56
+ entries: Map<string, TokenEntry>;
57
+
58
+ constructor() {
59
+ this.entries = new Map();
60
+ }
61
+
62
+ /**
63
+ * Set an entry in the token cache.
64
+ */
65
+ addEntry(
66
+ address: string,
67
+ username: string,
68
+ requestFn: OIDCRequestFunction | null,
69
+ refreshFn: OIDCRefreshFunction | null,
70
+ tokenResult: OIDCRequestTokenResult,
71
+ serverResult: OIDCMechanismServerStep1
72
+ ): TokenEntry {
73
+ const entry = new TokenEntry(
74
+ tokenResult,
75
+ serverResult,
76
+ expirationTime(tokenResult.expiresInSeconds)
77
+ );
78
+ this.entries.set(cacheKey(address, username, requestFn, refreshFn), entry);
79
+ return entry;
80
+ }
81
+
82
+ /**
83
+ * Clear the cache.
84
+ */
85
+ clear(): void {
86
+ this.entries.clear();
87
+ }
88
+
89
+ /**
90
+ * Delete an entry from the cache.
91
+ */
92
+ deleteEntry(
93
+ address: string,
94
+ username: string,
95
+ requestFn: OIDCRequestFunction | null,
96
+ refreshFn: OIDCRefreshFunction | null
97
+ ): void {
98
+ this.entries.delete(cacheKey(address, username, requestFn, refreshFn));
99
+ }
100
+
101
+ /**
102
+ * Get an entry from the cache.
103
+ */
104
+ getEntry(
105
+ address: string,
106
+ username: string,
107
+ requestFn: OIDCRequestFunction | null,
108
+ refreshFn: OIDCRefreshFunction | null
109
+ ): TokenEntry | undefined {
110
+ return this.entries.get(cacheKey(address, username, requestFn, refreshFn));
111
+ }
112
+
113
+ /**
114
+ * Delete all expired entries from the cache.
115
+ */
116
+ deleteExpiredEntries(): void {
117
+ for (const [key, entry] of this.entries) {
118
+ if (!entry.isValid()) {
119
+ this.entries.delete(key);
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get an expiration time in milliseconds past epoch. Defaults to immediate.
127
+ */
128
+ function expirationTime(expiresInSeconds?: number): number {
129
+ return Date.now() + (expiresInSeconds ?? DEFAULT_EXPIRATION_SECS) * 1000;
130
+ }
131
+
132
+ /**
133
+ * Create a cache key from the address and username.
134
+ */
135
+ function cacheKey(
136
+ address: string,
137
+ username: string,
138
+ requestFn: OIDCRequestFunction | null,
139
+ refreshFn: OIDCRefreshFunction | null
140
+ ): string {
141
+ return `${address}-${username}-${hashFunctions(requestFn, refreshFn)}`;
142
+ }
143
+
144
+ /**
145
+ * Get the hash string for the request and refresh functions.
146
+ */
147
+ function hashFunctions(
148
+ requestFn: OIDCRequestFunction | null,
149
+ refreshFn: OIDCRefreshFunction | null
150
+ ): string {
151
+ let requestHash = FN_HASHES.get(requestFn || NO_FUNCTION);
152
+ let refreshHash = FN_HASHES.get(refreshFn || NO_FUNCTION);
153
+ if (!requestHash && requestFn) {
154
+ // Create a new one for the function and put it in the map.
155
+ FN_HASH_COUNTER++;
156
+ requestHash = FN_HASH_COUNTER;
157
+ FN_HASHES.set(requestFn, FN_HASH_COUNTER);
158
+ }
159
+ if (!refreshHash && refreshFn) {
160
+ // Create a new one for the function and put it in the map.
161
+ FN_HASH_COUNTER++;
162
+ refreshHash = FN_HASH_COUNTER;
163
+ FN_HASHES.set(refreshFn, FN_HASH_COUNTER);
164
+ }
165
+ return `${requestHash}-${refreshHash}`;
166
+ }
@@ -0,0 +1,21 @@
1
+ import type { Document } from 'bson';
2
+
3
+ import type { Connection } from '../../connection';
4
+ import type { MongoCredentials } from '../mongo_credentials';
5
+
6
+ export interface Workflow {
7
+ /**
8
+ * All device workflows must implement this method in order to get the access
9
+ * token and then call authenticate with it.
10
+ */
11
+ execute(
12
+ connection: Connection,
13
+ credentials: MongoCredentials,
14
+ reauthenticate?: boolean
15
+ ): Promise<Document>;
16
+
17
+ /**
18
+ * Get the document to add for speculative authentication.
19
+ */
20
+ speculativeAuth(): Promise<Document>;
21
+ }
@@ -1,20 +1,28 @@
1
+ import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../error';
2
+ import type { HandshakeDocument } from '../connect';
3
+ import { type AuthContext, AuthProvider } from './auth_provider';
4
+ import type { MongoCredentials } from './mongo_credentials';
5
+ import { AwsServiceWorkflow } from './mongodb_oidc/aws_service_workflow';
6
+ import { CallbackWorkflow } from './mongodb_oidc/callback_workflow';
7
+ import type { Workflow } from './mongodb_oidc/workflow';
8
+
1
9
  /**
2
- * TODO: NODE-5035: Make API public
3
- *
4
- * @internal */
10
+ * @public
11
+ * @experimental
12
+ */
5
13
  export interface OIDCMechanismServerStep1 {
6
- authorizeEndpoint?: string;
14
+ authorizationEndpoint?: string;
7
15
  tokenEndpoint?: string;
8
- deviceAuthorizeEndpoint?: string;
16
+ deviceAuthorizationEndpoint?: string;
9
17
  clientId: string;
10
18
  clientSecret?: string;
11
19
  requestScopes?: string[];
12
20
  }
13
21
 
14
22
  /**
15
- * TODO: NODE-5035: Make API public
16
- *
17
- * @internal */
23
+ * @public
24
+ * @experimental
25
+ */
18
26
  export interface OIDCRequestTokenResult {
19
27
  accessToken: string;
20
28
  expiresInSeconds?: number;
@@ -22,18 +30,94 @@ export interface OIDCRequestTokenResult {
22
30
  }
23
31
 
24
32
  /**
25
- * TODO: NODE-5035: Make API public
26
- *
27
- * @internal */
33
+ * @public
34
+ * @experimental
35
+ */
28
36
  export type OIDCRequestFunction = (
29
- idl: OIDCMechanismServerStep1
37
+ principalName: string,
38
+ serverResult: OIDCMechanismServerStep1,
39
+ timeout: AbortSignal | number
30
40
  ) => Promise<OIDCRequestTokenResult>;
31
41
 
32
42
  /**
33
- * TODO: NODE-5035: Make API public
34
- *
35
- * @internal */
43
+ * @public
44
+ * @experimental
45
+ */
36
46
  export type OIDCRefreshFunction = (
37
- idl: OIDCMechanismServerStep1,
38
- result: OIDCRequestTokenResult
47
+ principalName: string,
48
+ serverResult: OIDCMechanismServerStep1,
49
+ result: OIDCRequestTokenResult,
50
+ timeout: AbortSignal | number
39
51
  ) => Promise<OIDCRequestTokenResult>;
52
+
53
+ type ProviderName = 'aws' | 'callback';
54
+
55
+ /** @internal */
56
+ export const OIDC_WORKFLOWS: Map<ProviderName, Workflow> = new Map();
57
+ OIDC_WORKFLOWS.set('callback', new CallbackWorkflow());
58
+ OIDC_WORKFLOWS.set('aws', new AwsServiceWorkflow());
59
+
60
+ /**
61
+ * OIDC auth provider.
62
+ * @experimental
63
+ */
64
+ export class MongoDBOIDC extends AuthProvider {
65
+ /**
66
+ * Instantiate the auth provider.
67
+ */
68
+ constructor() {
69
+ super();
70
+ }
71
+
72
+ /**
73
+ * Authenticate using OIDC
74
+ */
75
+ override async auth(authContext: AuthContext): Promise<void> {
76
+ const { connection, credentials, response, reauthenticating } = authContext;
77
+
78
+ if (response?.speculativeAuthenticate) {
79
+ return;
80
+ }
81
+
82
+ if (!credentials) {
83
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
84
+ }
85
+
86
+ const workflow = getWorkflow(credentials);
87
+
88
+ await workflow.execute(connection, credentials, reauthenticating);
89
+ }
90
+
91
+ /**
92
+ * Add the speculative auth for the initial handshake.
93
+ */
94
+ override async prepare(
95
+ handshakeDoc: HandshakeDocument,
96
+ authContext: AuthContext
97
+ ): Promise<HandshakeDocument> {
98
+ const { credentials } = authContext;
99
+
100
+ if (!credentials) {
101
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
102
+ }
103
+
104
+ const workflow = getWorkflow(credentials);
105
+
106
+ const result = await workflow.speculativeAuth();
107
+ return { ...handshakeDoc, ...result };
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Gets either a device workflow or callback workflow.
113
+ */
114
+ function getWorkflow(credentials: MongoCredentials): Workflow {
115
+ const providerName = credentials.mechanismProperties.PROVIDER_NAME;
116
+ const workflow = OIDC_WORKFLOWS.get(providerName || 'callback');
117
+ if (!workflow) {
118
+ throw new MongoInvalidArgumentError(
119
+ `Could not load workflow for provider ${credentials.mechanismProperties.PROVIDER_NAME}`
120
+ );
121
+ }
122
+ return workflow;
123
+ }
@@ -1,16 +1,16 @@
1
1
  import { Binary } from '../../bson';
2
2
  import { MongoMissingCredentialsError } from '../../error';
3
- import { Callback, ns } from '../../utils';
3
+ import { ns } from '../../utils';
4
4
  import { AuthContext, AuthProvider } from './auth_provider';
5
5
 
6
6
  export class Plain extends AuthProvider {
7
- override auth(authContext: AuthContext, callback: Callback): void {
7
+ override async auth(authContext: AuthContext): Promise<void> {
8
8
  const { connection, credentials } = authContext;
9
9
  if (!credentials) {
10
- return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.'));
10
+ throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
11
11
  }
12
- const username = credentials.username;
13
- const password = credentials.password;
12
+
13
+ const { username, password } = credentials;
14
14
 
15
15
  const payload = new Binary(Buffer.from(`\x00${username}\x00${password}`));
16
16
  const command = {
@@ -20,6 +20,6 @@ export class Plain extends AuthProvider {
20
20
  autoAuthorize: 1
21
21
  };
22
22
 
23
- connection.command(ns('$external.$cmd'), command, undefined, callback);
23
+ await connection.commandAsync(ns('$external.$cmd'), command, undefined);
24
24
  }
25
25
  }
@@ -8,12 +8,12 @@ export const AuthMechanism = Object.freeze({
8
8
  MONGODB_SCRAM_SHA1: 'SCRAM-SHA-1',
9
9
  MONGODB_SCRAM_SHA256: 'SCRAM-SHA-256',
10
10
  MONGODB_X509: 'MONGODB-X509',
11
- /** @internal TODO: NODE-5035: Make mechanism public. */
11
+ /** @experimental */
12
12
  MONGODB_OIDC: 'MONGODB-OIDC'
13
13
  } as const);
14
14
 
15
15
  /** @public */
16
- export type AuthMechanism = typeof AuthMechanism[keyof typeof AuthMechanism];
16
+ export type AuthMechanism = (typeof AuthMechanism)[keyof typeof AuthMechanism];
17
17
 
18
18
  /** @internal */
19
19
  export const AUTH_MECHS_AUTH_SRC_EXTERNAL = new Set<AuthMechanism>([