diff --git a/.changeset/four-pugs-move.md b/.changeset/four-pugs-move.md new file mode 100644 index 00000000000..7f9cd3636c4 --- /dev/null +++ b/.changeset/four-pugs-move.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Prevent a bug that caused all sessions being marked as logged out if some required value was missing due to a race condition. diff --git a/apps/meteor/app/statistics/server/lib/SAUMonitor.ts b/apps/meteor/app/statistics/server/lib/SAUMonitor.ts index aee71029263..32c70ccb299 100644 --- a/apps/meteor/app/statistics/server/lib/SAUMonitor.ts +++ b/apps/meteor/app/statistics/server/lib/SAUMonitor.ts @@ -127,9 +127,26 @@ export class SAUMonitorClass { if (!this.isRunning()) { return; } + + if (!userId) { + logger.warn(`Received 'accounts.logout' event without 'userId'`); + return; + } + const { id: sessionId } = connection; + if (!sessionId) { + logger.warn(`Received 'accounts.logout' event without 'sessionId'`); + return; + } + + const session = await Sessions.getLoggedInByUserIdAndSessionId>(userId, sessionId, { + projection: { loginToken: 1 }, + }); + if (!session?.loginToken) { + throw new Error('Session not found'); + } - await Sessions.logoutBySessionIdAndUserId({ sessionId, userId }); + await Sessions.logoutBySessionIdAndUserId({ loginToken: session.loginToken, userId }); }); } diff --git a/packages/model-typings/src/models/ISessionsModel.ts b/packages/model-typings/src/models/ISessionsModel.ts index 1e6a36fd6f7..571d8c170ad 100644 --- a/packages/model-typings/src/models/ISessionsModel.ts +++ b/packages/model-typings/src/models/ISessionsModel.ts @@ -7,7 +7,7 @@ import type { DeviceManagementPopulatedSession, DeviceManagementSession, } from '@rocket.chat/core-typings'; -import type { BulkWriteResult, Document, UpdateResult, FindCursor, OptionalId } from 'mongodb'; +import type { BulkWriteResult, Document, FindOptions, UpdateResult, FindCursor, OptionalId } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; @@ -127,10 +127,10 @@ export interface ISessionsModel extends IBaseModel { logoutByInstanceIdAndSessionIdAndUserId(instanceId: string, sessionId: string, userId: string): Promise; logoutBySessionIdAndUserId({ - sessionId, + loginToken, userId, }: { - sessionId: ISession['sessionId']; + loginToken: ISession['loginToken']; userId: IUser['_id']; }): Promise; @@ -149,4 +149,10 @@ export interface ISessionsModel extends IBaseModel { updateDailySessionById(_id: ISession['_id'], record: Partial): Promise; updateAllSessionsByDateToComputed({ start, end }: DestructuredRange): Promise; + + getLoggedInByUserIdAndSessionId( + userId: string, + sessionId: string, + options?: FindOptions, + ): Promise; } diff --git a/packages/models/src/models/Sessions.ts b/packages/models/src/models/Sessions.ts index c89ca9d3403..ae0ef369a11 100644 --- a/packages/models/src/models/Sessions.ts +++ b/packages/models/src/models/Sessions.ts @@ -25,6 +25,7 @@ import type { IndexDescription, UpdateResult, OptionalId, + FindOptions, } from 'mongodb'; import { getCollectionName } from '../index'; @@ -1534,19 +1535,12 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { } async logoutBySessionIdAndUserId({ - sessionId, + loginToken, userId, }: { - sessionId: ISession['sessionId']; + loginToken: ISession['loginToken']; userId: IUser['_id']; }): Promise { - const query = { - sessionId, - userId, - logoutAt: { $exists: false }, - }; - const session = await this.findOne>(query, { projection: { loginToken: 1 } }); - const logoutAt = new Date(); const updateObj = { $set: { @@ -1556,7 +1550,7 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { }, }; - return this.updateMany({ userId, loginToken: session?.loginToken }, updateObj); + return this.updateMany({ userId, loginToken }, updateObj); } async logoutByloginTokenAndUserId({ @@ -1622,4 +1616,12 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { }, ); } + + async getLoggedInByUserIdAndSessionId( + userId: string, + sessionId: string, + options?: FindOptions, + ): Promise { + return this.findOne({ userId, sessionId, logoutAt: { $exists: false } }, options); + } }