From 305a9599043da4be31b37bc88e5f53576a3bd80e Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 1 Aug 2023 16:18:04 -0400 Subject: [PATCH] feat(NODE-5233)!: prevent session from one client from being used on another --- src/mongo_client.ts | 9 ++++- src/operations/execute_operation.ts | 2 + .../sessions/sessions.prose.test.ts | 37 ++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index eb5b5ac06ea..d7bb57c07bf 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -589,7 +589,14 @@ export class MongoClient extends TypedEventEmitter { return client.connect(); } - /** Starts a new session on the server */ + /** + * Creates a new ClientSession. When using the returned session in an operation + * a corresponding ServerSession will be created. + * + * @remarks + * A ClientSession instance may only be passed to operations being performed on the same + * MongoClient it was started from. + */ startSession(options?: ClientSessionOptions): ClientSession { const session = new ClientSession( this, diff --git a/src/operations/execute_operation.ts b/src/operations/execute_operation.ts index 50f77648f97..5424858e0aa 100644 --- a/src/operations/execute_operation.ts +++ b/src/operations/execute_operation.ts @@ -118,6 +118,8 @@ async function executeOperationAsync< throw new MongoExpiredSessionError('Use of expired sessions is not permitted'); } else if (session.snapshotEnabled && !topology.capabilities.supportsSnapshotReads) { throw new MongoCompatibilityError('Snapshot reads require MongoDB 5.0 or later'); + } else if (session.client !== client) { + throw new MongoRuntimeError('ClientSession must be from the same MongoClient'); } const readPreference = operation.readPreference ?? ReadPreference.primary; diff --git a/test/integration/sessions/sessions.prose.test.ts b/test/integration/sessions/sessions.prose.test.ts index ee851e5f318..b880e12ac8c 100644 --- a/test/integration/sessions/sessions.prose.test.ts +++ b/test/integration/sessions/sessions.prose.test.ts @@ -6,11 +6,46 @@ import { type Collection, type CommandStartedEvent, MongoClient, - MongoDriverError + MongoDriverError, + MongoRuntimeError } from '../../mongodb'; import { sleep } from '../../tools/utils'; describe('Sessions Prose Tests', () => { + describe.only('5. Session argument is for the right client', () => { + let client1: MongoClient; + let client2: MongoClient; + beforeEach(async function () { + client1 = this.configuration.newClient(); + client2 = this.configuration.newClient(); + }); + + afterEach(async function () { + await client1?.close(); + await client2?.close(); + }); + + /** + * Steps: + * - Create client1 and client2 + * - Get database from client1 + * - Get collection from database + * - Start session from client2 + * - Call collection.insertOne(session,...) + * - Assert that an error was reported because session was not started from client1 + */ + context('when session is started from a different client than operation is being run on', () => + it('the operation throws a MongoRuntimeError', async () => { + const db = client1.db(); + const collection = db.collection('test'); + const session = client2.startSession(); + const error = await collection.insertOne({}, { session }).catch(error => error); + expect(error).to.be.instanceOf(MongoRuntimeError); + expect(error).to.match(/ClientSession must be from the same MongoClient/i); + }) + ); + }); + describe('14. Implicit sessions only allocate their server session after a successful connection checkout', () => { let client: MongoClient; let testCollection: Collection<{ _id: number; a?: number }>;