From fbd054015224ec70d00b6750d442524d4eab8dbb Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Tue, 12 May 2026 22:37:32 +0200 Subject: [PATCH] fix: clean up login tokens in users.deactivateidle (#40496) --- .changeset/good-rules-lie.md | 7 ++++++ apps/meteor/app/api/server/v1/users.ts | 13 ++++++++++- apps/meteor/tests/end-to-end/api/users.ts | 23 +++++++++++++++++++ .../model-typings/src/models/IUsersModel.ts | 1 + packages/models/src/models/Users.ts | 16 ++++++++++++- 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 .changeset/good-rules-lie.md diff --git a/.changeset/good-rules-lie.md b/.changeset/good-rules-lie.md new file mode 100644 index 0000000000000..75542e75aa078 --- /dev/null +++ b/.changeset/good-rules-lie.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/model-typings': patch +'@rocket.chat/models': patch +'@rocket.chat/meteor': patch +--- + +Ensures that deactivated users have their login tokens cleaned up in users.deactivateidle diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 88bc701570a6a..3e97ae5e84d45 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -420,9 +420,20 @@ API.v1.addRoute( const lastLoggedIn = new Date(); lastLoggedIn.setDate(lastLoggedIn.getDate() - daysIdle); - // since we're deactiving users that are not logged in, there is no need to send data through WS + const ids = await Users.findActiveNotLoggedInAfterWithRole(lastLoggedIn, role, { projection: { _id: 1 } }) + .map(({ _id }: { _id: string }) => _id) + .toArray(); + const { modifiedCount: count } = await Users.setActiveNotLoggedInAfterWithRole(lastLoggedIn, role, false); + ids.forEach((_id) => { + void notifyOnUserChange({ + clientAction: 'updated', + id: _id, + diff: { 'services.resume.loginTokens': [], 'active': false }, + }); + }); + return API.v1.success({ count, }); diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index e5e57c709fb0d..1ea2a28260191 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -4012,6 +4012,29 @@ describe('[Users]', () => { .end(done); }); }); + + it('should revoke login tokens of deactivated idle users', async () => { + const idleUser = await createUser(); + await request.post(api('roles.addUserToRole')).set(credentials).send({ roleId: testRoleId, username: idleUser.username }).expect(200); + + const idleUserCredentials = await login(idleUser.username, password); + await request.get(api('me')).set(idleUserCredentials).expect(200); + + await updatePermission('edit-other-user-active-status', ['admin']); + await request + .post(api('users.deactivateIdle')) + .set(credentials) + .send({ daysIdle: 0, role: testRoleId }) + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count').that.is.greaterThan(0); + }); + + await request.get(api('me')).set(idleUserCredentials).expect(401); + + await deleteUser(idleUser); + }); }); describe('[/users.requestDataDownload]', () => { diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 2d50bfd377cb9..cebb172497dc8 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -389,6 +389,7 @@ export interface IUsersModel extends IBaseModel { setUserActive(userId: string, active: boolean): Promise; setAllUsersActive(active: boolean): Promise; setActiveNotLoggedInAfterWithRole(latestLastLoginDate: Date, role?: string, active?: boolean): Promise; + findActiveNotLoggedInAfterWithRole(latestLastLoginDate: Date, role?: string, options?: FindOptions): FindCursor; unsetRequirePasswordChange(userId: string): Promise; resetPasswordAndSetRequirePasswordChange( userId: string, diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index 0481483ccfb54..90fcc25e133c4 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -2982,15 +2982,29 @@ export class UsersRaw extends BaseRaw> implements IU roles: role, }; - const update = { + const update: UpdateFilter = { $set: { active, + ...(!active && { 'services.resume.loginTokens': [] }), }, }; return this.updateMany(query, update); } + findActiveNotLoggedInAfterWithRole(latestLastLoginDate: Date, role: IRole['_id'] = 'user', options: FindOptions = {}) { + const neverActive = { lastLogin: { $exists: false }, createdAt: { $lte: latestLastLoginDate } }; + const idleTooLong = { lastLogin: { $lte: latestLastLoginDate } }; + + const query = { + $or: [neverActive, idleTooLong], + active: true, + roles: role, + }; + + return this.find(query, options); + } + unsetRequirePasswordChange(_id: IUser['_id']) { const update: UpdateFilter = { $unset: {