Skip to content

Commit 3e70911

Browse files
committed
feat(NODE-5233)!: prevent session from one client from being used on another
1 parent 0c5c0b4 commit 3e70911

File tree

3 files changed

+46
-2
lines changed

3 files changed

+46
-2
lines changed

Diff for: src/mongo_client.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,14 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
598598
return client.connect();
599599
}
600600

601-
/** Starts a new session on the server */
601+
/**
602+
* Creates a new ClientSession. When using the returned session in an operation
603+
* a corresponding ServerSession will be created.
604+
*
605+
* @remarks
606+
* A ClientSession instance may only be passed to operations being performed on the same
607+
* MongoClient it was started from.
608+
*/
602609
startSession(options?: ClientSessionOptions): ClientSession {
603610
const session = new ClientSession(
604611
this,

Diff for: src/operations/execute_operation.ts

+2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ async function executeOperationAsync<
118118
throw new MongoExpiredSessionError('Use of expired sessions is not permitted');
119119
} else if (session.snapshotEnabled && !topology.capabilities.supportsSnapshotReads) {
120120
throw new MongoCompatibilityError('Snapshot reads require MongoDB 5.0 or later');
121+
} else if (session.client !== client) {
122+
throw new MongoRuntimeError('ClientSession must be from the same MongoClient');
121123
}
122124

123125
const readPreference = operation.readPreference ?? ReadPreference.primary;

Diff for: test/integration/sessions/sessions.prose.test.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,46 @@ import {
66
type Collection,
77
type CommandStartedEvent,
88
MongoClient,
9-
MongoDriverError
9+
MongoDriverError,
10+
MongoRuntimeError
1011
} from '../../mongodb';
1112
import { sleep } from '../../tools/utils';
1213

1314
describe('Sessions Prose Tests', () => {
15+
describe('5. Session argument is for the right client', () => {
16+
let client1: MongoClient;
17+
let client2: MongoClient;
18+
beforeEach(async function () {
19+
client1 = this.configuration.newClient();
20+
client2 = this.configuration.newClient();
21+
});
22+
23+
afterEach(async function () {
24+
await client1?.close();
25+
await client2?.close();
26+
});
27+
28+
/**
29+
* Steps:
30+
* - Create client1 and client2
31+
* - Get database from client1
32+
* - Get collection from database
33+
* - Start session from client2
34+
* - Call collection.insertOne(session,...)
35+
* - Assert that an error was reported because session was not started from client1
36+
*/
37+
context('when session is started from a different client than operation is being run on', () =>
38+
it('the operation throws a MongoRuntimeError', async () => {
39+
const db = client1.db();
40+
const collection = db.collection('test');
41+
const session = client2.startSession();
42+
const error = await collection.insertOne({}, { session }).catch(error => error);
43+
expect(error).to.be.instanceOf(MongoRuntimeError);
44+
expect(error).to.match(/ClientSession must be from the same MongoClient/i);
45+
})
46+
);
47+
});
48+
1449
describe('14. Implicit sessions only allocate their server session after a successful connection checkout', () => {
1550
let client: MongoClient;
1651
let testCollection: Collection<{ _id: number; a?: number }>;

0 commit comments

Comments
 (0)