Skip to content

Commit 847c68e

Browse files
authored
Merge branch 'main' into NODE-7335-bundle-and-barrel-approach-poc
2 parents 81e9151 + 4c89408 commit 847c68e

70 files changed

Lines changed: 6648 additions & 1525 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@
282282
"**/../lib/**",
283283
"mongodb-mock-server",
284284
"node:*",
285-
"os"
285+
"os",
286+
"crypto"
286287
],
287288
"paths": [
288289
{

.evergreen/config.in.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,8 @@ tasks:
997997
- { key: VERSION, value: latest }
998998
- { key: NODE_LTS_VERSION, value: "20.19.0" }
999999
- func: install dependencies
1000+
vars:
1001+
NPM_VERSION: "11.11.1"
10001002
- func: switch source
10011003
vars:
10021004
SOURCE_REV: refs/tags/v7.0.0
@@ -1019,6 +1021,8 @@ tasks:
10191021
- { key: CLIENT_ENCRYPTION, value: "false" }
10201022
- { key: NODE_LTS_VERSION, value: "20.19.0" }
10211023
- func: install dependencies
1024+
vars:
1025+
NPM_VERSION: "11.11.1"
10221026
- func: bootstrap mongo-orchestration
10231027
- func: switch source
10241028
vars:
@@ -1042,6 +1046,8 @@ tasks:
10421046
- { key: CLIENT_ENCRYPTION, value: "false" }
10431047
- { key: NODE_LTS_VERSION, value: "20.19.0" }
10441048
- func: install dependencies
1049+
vars:
1050+
NPM_VERSION: "11.11.1"
10451051
- func: bootstrap mongo-orchestration
10461052
- func: switch source
10471053
vars:
@@ -1065,6 +1071,8 @@ tasks:
10651071
- { key: CLIENT_ENCRYPTION, value: "false" }
10661072
- { key: NODE_LTS_VERSION, value: "20.19.0" }
10671073
- func: install dependencies
1074+
vars:
1075+
NPM_VERSION: "11.11.1"
10681076
- func: bootstrap mongo-orchestration
10691077
- func: switch source
10701078
vars:

.evergreen/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,8 @@ tasks:
921921
- {key: VERSION, value: latest}
922922
- {key: NODE_LTS_VERSION, value: 20.19.0}
923923
- func: install dependencies
924+
vars:
925+
NPM_VERSION: 11.11.1
924926
- func: switch source
925927
vars:
926928
SOURCE_REV: refs/tags/v7.0.0
@@ -942,6 +944,8 @@ tasks:
942944
- {key: CLIENT_ENCRYPTION, value: 'false'}
943945
- {key: NODE_LTS_VERSION, value: 20.19.0}
944946
- func: install dependencies
947+
vars:
948+
NPM_VERSION: 11.11.1
945949
- func: bootstrap mongo-orchestration
946950
- func: switch source
947951
vars:
@@ -964,6 +968,8 @@ tasks:
964968
- {key: CLIENT_ENCRYPTION, value: 'false'}
965969
- {key: NODE_LTS_VERSION, value: 20.19.0}
966970
- func: install dependencies
971+
vars:
972+
NPM_VERSION: 11.11.1
967973
- func: bootstrap mongo-orchestration
968974
- func: switch source
969975
vars:
@@ -986,6 +992,8 @@ tasks:
986992
- {key: CLIENT_ENCRYPTION, value: 'false'}
987993
- {key: NODE_LTS_VERSION, value: 20.19.0}
988994
- func: install dependencies
995+
vars:
996+
NPM_VERSION: 11.11.1
989997
- func: bootstrap mongo-orchestration
990998
- func: switch source
991999
vars:

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: "CodeQL"
22

33
on:
44
push:
5-
branches: [ "main", "5.x" ]
5+
branches: [ "main", "v7.1.x" ]
66
pull_request:
7-
branches: [ "main", "5.x" ]
7+
branches: [ "main", "v7.1.x" ]
88

99
jobs:
1010
analyze:

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ If you run into any unexpected compiler failures against our supported TypeScrip
101101

102102
Additionally, our Typescript types are compatible with the ECMAScript standard for our minimum supported Node version. Currently, our Typescript targets es2023.
103103

104+
#### Running in Custom Runtimes
105+
106+
We are working on removing Node.js as a dependency of the driver, so that in the future it will be possible to use the driver in non-Node environments.
107+
This work is currently in progress, and if you're curious, this is [our first runtime adapter commit](https://github.com/mongodb/node-mongodb-native/commit/d2ad07f20903d86334da81222a6df9717f76faaa).
108+
109+
Some things to keep in mind if you are using a non-Node runtime:
110+
111+
1. Users of Webpack/Vite may need to prevent `crypto` polyfill injection.
112+
2. Auth mechanism `SCRAM-SHA-1` has a hard dependency on Node.js.
113+
3. Auth mechanism `SCRAM-SHA-1` is not supported in FIPS mode.
114+
104115
## Installation
105116

106117
The recommended way to get started using the Node.js driver is by using the `npm` (Node Package Manager) to install the dependency in your project.

etc/notes/releasing.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ Fixes are usually released in either the next patch version or in the next minor
3030

3131
release-please automatically tags release commits with a tag in the format v<major>.<minor>.<patch>. When backporting, first determine the target minor version and create a release branch for it by branching off of the release tag. The release branch should follow the format `v<major>.<minor>.x`. For example, to create a backport of bson's 6.5 release, create a release branch from the v6.5.0 tag with the name v6.5.x.
3232

33-
Then, backport the release action to the target release branch. First, create a copy of our current release action (release.yml). Then, change any references to `main` to the target branch. Double check that there isn't any release tooling on main that doesn't exist on the target branch. If there is, make sure this is backported too. Check if the target branch has a release-please config and manifest file. If not, make sure to adopt changes for release-please v4 (see https://github.com/mongodb/js-bson/pull/682 as an example). Backport all of the above changes to the target release branch.
33+
Then, backport the release action to the target release branch. This should be done as the first PR on the release branch. Use the following instructions to create it.
3434

35-
Now, the release-please will work the same as `main`. Any PRs that merge to the release branch trigger the release action and update release-pleases' release PR. Proceed as normal from here.
35+
First, create a copy of our current release action (release.yml). Then, change any references to `main` to the target branch. Also, add the branch to the list of CodeQL target branches (codeql.yml). Double check that there isn't any release tooling on `main` that doesn't exist on the target branch. If there is, make sure this is backported too. After opening the PR, check the CI to make sure everything works as expected: add backports of CI fixes as needed.
36+
37+
Once the release action PR is merged, release-please will work on this branch in the same way as it does on `main`. Any PRs that merge to the release branch trigger the release action and update the release PR that release-please manages. Proceed as normal from here.
3638

3739
## `release-please`
3840

src/change_stream.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,11 @@ export class ChangeStream<
705705
return this.cursor?.resumeToken;
706706
}
707707

708+
/** Returns the currently buffered documents length of the underlying cursor. */
709+
bufferedCount(): number {
710+
return this.cursor?.bufferedCount() ?? 0;
711+
}
712+
708713
/** Check if there is any document still available in the Change Stream */
709714
async hasNext(): Promise<boolean> {
710715
this._setIsIterator();

src/cmap/auth/gssapi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export async function performGSSAPICanonicalizeHostName(
169169

170170
try {
171171
// Perform a reverse ptr lookup on the ip address.
172-
const results = await dns.promises.resolvePtr(address);
172+
const results = await dns.promises.resolve(address, 'PTR');
173173
// If the ptr did not error but had no results, return the host.
174174
return results.length > 0 ? results[0] : host;
175175
} catch {
@@ -188,7 +188,7 @@ export async function performGSSAPICanonicalizeHostName(
188188
export async function resolveCname(host: string): Promise<string> {
189189
// Attempt to resolve the host name
190190
try {
191-
const results = await dns.promises.resolveCname(host);
191+
const results = await dns.promises.resolve(host, 'CNAME');
192192
// Get the first resolved host id
193193
return results.length > 0 ? results[0] : host;
194194
} catch {

src/cmap/auth/scram.ts

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { saslprep } from '@mongodb-js/saslprep';
2-
import * as crypto from 'crypto';
32

43
import { Binary, ByteUtils, type Document } from '../../bson';
54
import {
@@ -157,27 +156,27 @@ async function continueScramConversation(
157156

158157
// Set up start of proof
159158
const withoutProof = `c=biws,r=${rnonce}`;
160-
const saltedPassword = HI(
159+
const saltedPassword = await HI(
161160
processedPassword,
162161
ByteUtils.fromBase64(salt),
163162
iterations,
164163
cryptoMethod
165164
);
166165

167-
const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
168-
const serverKey = HMAC(cryptoMethod, saltedPassword, 'Server Key');
169-
const storedKey = H(cryptoMethod, clientKey);
166+
const clientKey = await HMAC(cryptoMethod, saltedPassword, 'Client Key');
167+
const serverKey = await HMAC(cryptoMethod, saltedPassword, 'Server Key');
168+
const storedKey = await H(cryptoMethod, clientKey);
170169
const authMessage = [
171170
clientFirstMessageBare(username, nonce),
172171
payload.toString('utf8'),
173172
withoutProof
174173
].join(',');
175174

176-
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
175+
const clientSignature = await HMAC(cryptoMethod, storedKey, authMessage);
177176
const clientProof = `p=${xor(clientKey, clientSignature)}`;
178177
const clientFinal = [withoutProof, clientProof].join(',');
179178

180-
const serverSignature = HMAC(cryptoMethod, serverKey, authMessage);
179+
const serverSignature = await HMAC(cryptoMethod, serverKey, authMessage);
181180
const saslContinueCmd = {
182181
saslContinue: 1,
183182
conversationId: response.conversationId,
@@ -229,19 +228,32 @@ function passwordDigest(username: string, password: string) {
229228
throw new MongoInvalidArgumentError('Password cannot be empty');
230229
}
231230

232-
let md5: crypto.Hash;
231+
let nodeCrypto;
233232
try {
234-
md5 = crypto.createHash('md5');
233+
// TODO: NODE-7424 - remove dependency on 'crypto' for SCRAM-SHA-1 authentication
234+
// eslint-disable-next-line @typescript-eslint/no-require-imports
235+
nodeCrypto = require('crypto');
236+
} catch (e) {
237+
throw new MongoRuntimeError(
238+
'Node.js crypto module is required for SCRAM-SHA-1 authentication',
239+
{
240+
cause: e
241+
}
242+
);
243+
}
244+
245+
try {
246+
const md5 = nodeCrypto.createHash('md5');
247+
md5.update(`${username}:mongo:${password}`, 'utf8');
248+
return md5.digest('hex');
235249
} catch (err) {
236-
if (crypto.getFips()) {
250+
if (nodeCrypto.getFips()) {
237251
// This error is (slightly) more helpful than what comes from OpenSSL directly, e.g.
238252
// 'Error: error:060800C8:digital envelope routines:EVP_DigestInit_ex:disabled for FIPS'
239253
throw new Error('Auth mechanism SCRAM-SHA-1 is not supported in FIPS mode');
240254
}
241255
throw err;
242256
}
243-
md5.update(`${username}:mongo:${password}`, 'utf8');
244-
return md5.digest('hex');
245257
}
246258

247259
// XOR two buffers
@@ -256,12 +268,28 @@ function xor(a: Uint8Array, b: Uint8Array) {
256268
return ByteUtils.toBase64(ByteUtils.fromNumberArray(res));
257269
}
258270

259-
function H(method: CryptoMethod, text: Uint8Array): Uint8Array {
260-
return crypto.createHash(method).update(text).digest();
271+
async function H(method: CryptoMethod, text: Uint8Array): Promise<Uint8Array> {
272+
const buffer = await crypto.subtle.digest(method === 'sha256' ? 'SHA-256' : 'SHA-1', text);
273+
return new Uint8Array(buffer);
261274
}
262275

263-
function HMAC(method: CryptoMethod, key: Uint8Array, text: Uint8Array | string): Uint8Array {
264-
return crypto.createHmac(method, key).update(text).digest();
276+
async function HMAC(
277+
method: CryptoMethod,
278+
key: Uint8Array,
279+
text: Uint8Array | string
280+
): Promise<Uint8Array> {
281+
const keyBuffer = ByteUtils.toLocalBufferType(key);
282+
const cryptoKey = await crypto.subtle.importKey(
283+
'raw',
284+
keyBuffer,
285+
{ name: 'HMAC', hash: { name: method === 'sha256' ? 'SHA-256' : 'SHA-1' } },
286+
false,
287+
['sign', 'verify']
288+
);
289+
const textData: Uint8Array = typeof text === 'string' ? new TextEncoder().encode(text) : text;
290+
const textBuffer = ByteUtils.toLocalBufferType(textData);
291+
const signature = await crypto.subtle.sign('HMAC', cryptoKey, textBuffer);
292+
return new Uint8Array(signature);
265293
}
266294

267295
interface HICache {
@@ -280,21 +308,32 @@ const hiLengthMap = {
280308
sha1: 20
281309
};
282310

283-
function HI(data: string, salt: Uint8Array, iterations: number, cryptoMethod: CryptoMethod) {
311+
async function HI(data: string, salt: Uint8Array, iterations: number, cryptoMethod: CryptoMethod) {
284312
// omit the work if already generated
285313
const key = [data, ByteUtils.toBase64(salt), iterations].join('_');
286314
if (_hiCache[key] != null) {
287315
return _hiCache[key];
288316
}
289317

290-
// generate the salt
291-
const saltedData = crypto.pbkdf2Sync(
292-
data,
293-
salt,
294-
iterations,
295-
hiLengthMap[cryptoMethod],
296-
cryptoMethod
318+
const keyMaterial = await crypto.subtle.importKey(
319+
'raw',
320+
new TextEncoder().encode(data),
321+
{ name: 'PBKDF2' },
322+
false,
323+
['deriveBits']
324+
);
325+
const params = {
326+
name: 'PBKDF2',
327+
salt: salt,
328+
iterations: iterations,
329+
hash: { name: cryptoMethod === 'sha256' ? 'SHA-256' : 'SHA-1' }
330+
};
331+
const derivedBits = await crypto.subtle.deriveBits(
332+
params,
333+
keyMaterial,
334+
hiLengthMap[cryptoMethod] * 8
297335
);
336+
const saltedData = new Uint8Array(derivedBits);
298337

299338
// cache a copy to speed up the next lookup, but prevent unbounded cache growth
300339
if (_hiCacheCount >= 200) {
@@ -311,10 +350,6 @@ function compareDigest(lhs: Uint8Array, rhs: Uint8Array) {
311350
return false;
312351
}
313352

314-
if (typeof crypto.timingSafeEqual === 'function') {
315-
return crypto.timingSafeEqual(lhs, rhs);
316-
}
317-
318353
let result = 0;
319354
for (let i = 0; i < lhs.length; i++) {
320355
result |= lhs[i] ^ rhs[i];

0 commit comments

Comments
 (0)