From 2bd8c98396d283a39107bc3e90ff6c950f9efd78 Mon Sep 17 00:00:00 2001
From: Diego Sampaio <chinello@gmail.com>
Date: Tue, 2 Jul 2024 10:45:22 -0300
Subject: [PATCH] fix: imported fixes (#32690)

---
 .changeset/witty-penguins-rush.md             |   5 +
 apps/meteor/app/api/server/api.ts             |   4 +
 .../app/livechat/server/api/v1/message.ts     |   2 +-
 .../livechat/server/methods/loadHistory.ts    |   9 +-
 .../livechat/server/methods/loginByToken.ts   |   1 +
 .../slashcommands-inviteall/server/server.ts  |   9 ++
 apps/meteor/server/models/raw/Team.ts         |   8 +-
 apps/meteor/tests/data/api-data.js            |   4 +
 apps/meteor/tests/end-to-end/api/01-users.js  |  98 +++++++++++++
 .../tests/end-to-end/api/16-commands.js       | 130 ++++++++++++++++++
 apps/meteor/tests/end-to-end/api/25-teams.js  |  99 +++++++++++--
 .../end-to-end/api/livechat/20-messages.ts    |  60 +++++++-
 .../api/livechat/methods/loadHistory.ts       |  61 ++++++++
 .../api/livechat/methods/loginByToken.ts      |  66 +++++++++
 14 files changed, 536 insertions(+), 20 deletions(-)
 create mode 100644 .changeset/witty-penguins-rush.md
 create mode 100644 apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts
 create mode 100644 apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts

diff --git a/.changeset/witty-penguins-rush.md b/.changeset/witty-penguins-rush.md
new file mode 100644
index 00000000000..632026d6fe2
--- /dev/null
+++ b/.changeset/witty-penguins-rush.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)
diff --git a/apps/meteor/app/api/server/api.ts b/apps/meteor/app/api/server/api.ts
index 5a87843f479..fef2f2165ef 100644
--- a/apps/meteor/app/api/server/api.ts
+++ b/apps/meteor/app/api/server/api.ts
@@ -63,6 +63,7 @@ interface IAPIDefaultFieldsToExclude {
 	statusDefault: number;
 	_updatedAt: number;
 	settings: number;
+	inviteToken: number;
 }
 
 type RateLimiterOptions = {
@@ -149,6 +150,7 @@ export class APIClass<TBasePath extends string = ''> extends Restivus {
 
 	public limitedUserFieldsToExcludeIfIsPrivilegedUser: {
 		services: number;
+		inviteToken: number;
 	};
 
 	constructor(properties: IAPIProperties) {
@@ -176,10 +178,12 @@ export class APIClass<TBasePath extends string = ''> extends Restivus {
 			statusDefault: 0,
 			_updatedAt: 0,
 			settings: 0,
+			inviteToken: 0,
 		};
 		this.limitedUserFieldsToExclude = this.defaultLimitedUserFieldsToExclude;
 		this.limitedUserFieldsToExcludeIfIsPrivilegedUser = {
 			services: 0,
+			inviteToken: 0,
 		};
 	}
 
diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts
index 1dcf54e403a..97c92eeb530 100644
--- a/apps/meteor/app/livechat/server/api/v1/message.ts
+++ b/apps/meteor/app/livechat/server/api/v1/message.ts
@@ -98,7 +98,7 @@ API.v1.addRoute(
 				throw new Error('invalid-room');
 			}
 
-			let message = await Messages.findOneById(_id);
+			let message = await Messages.findOneByRoomIdAndMessageId(rid, _id);
 			if (!message) {
 				throw new Error('invalid-message');
 			}
diff --git a/apps/meteor/app/livechat/server/methods/loadHistory.ts b/apps/meteor/app/livechat/server/methods/loadHistory.ts
index 8d747cad20d..373ede1a361 100644
--- a/apps/meteor/app/livechat/server/methods/loadHistory.ts
+++ b/apps/meteor/app/livechat/server/methods/loadHistory.ts
@@ -1,6 +1,7 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { LivechatVisitors, LivechatRooms } from '@rocket.chat/models';
 import type { ServerMethods } from '@rocket.chat/ui-contexts';
+import { check, Match } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
 import { loadMessageHistory } from '../../../lib/server/functions/loadMessageHistory';
@@ -23,9 +24,11 @@ Meteor.methods<ServerMethods>({
 	async 'livechat:loadHistory'({ token, rid, end, limit = 20, ls }) {
 		methodDeprecationLogger.method('livechat:loadHistory', '7.0.0');
 
-		if (!token || typeof token !== 'string') {
-			return;
-		}
+		check(token, String);
+		check(rid, String);
+		check(end, Date);
+		check(ls, Match.OneOf(String, Date));
+		check(limit, Number);
 
 		const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } });
 
diff --git a/apps/meteor/app/livechat/server/methods/loginByToken.ts b/apps/meteor/app/livechat/server/methods/loginByToken.ts
index 54ba97e8992..cae23e6d16f 100644
--- a/apps/meteor/app/livechat/server/methods/loginByToken.ts
+++ b/apps/meteor/app/livechat/server/methods/loginByToken.ts
@@ -14,6 +14,7 @@ declare module '@rocket.chat/ui-contexts' {
 Meteor.methods<ServerMethods>({
 	async 'livechat:loginByToken'(token) {
 		methodDeprecationLogger.method('livechat:loginByToken', '7.0.0');
+		check(token, String);
 		const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } });
 
 		if (!visitor) {
diff --git a/apps/meteor/app/slashcommands-inviteall/server/server.ts b/apps/meteor/app/slashcommands-inviteall/server/server.ts
index 5376bd6ae64..bac4349ec72 100644
--- a/apps/meteor/app/slashcommands-inviteall/server/server.ts
+++ b/apps/meteor/app/slashcommands-inviteall/server/server.ts
@@ -10,6 +10,7 @@ import { Meteor } from 'meteor/meteor';
 
 import { isTruthy } from '../../../lib/isTruthy';
 import { i18n } from '../../../server/lib/i18n';
+import { canAccessRoomAsync } from '../../authorization/server';
 import { addUsersToRoomMethod } from '../../lib/server/methods/addUsersToRoom';
 import { createChannelMethod } from '../../lib/server/methods/createChannel';
 import { createPrivateGroupMethod } from '../../lib/server/methods/createPrivateGroup';
@@ -55,6 +56,14 @@ function inviteAll<T extends string>(type: T): SlashCommand<T>['callback'] {
 			});
 			return;
 		}
+
+		if (!(await canAccessRoomAsync(baseChannel, user))) {
+			void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
+				msg: i18n.t('Room_not_exist_or_not_permission', { lng }),
+			});
+			return;
+		}
+
 		const cursor = Subscriptions.findByRoomIdWhenUsernameExists(baseChannel._id, {
 			projection: { 'u.username': 1 },
 		});
diff --git a/apps/meteor/server/models/raw/Team.ts b/apps/meteor/server/models/raw/Team.ts
index c5c8b5f286d..5f4b6765eb2 100644
--- a/apps/meteor/server/models/raw/Team.ts
+++ b/apps/meteor/server/models/raw/Team.ts
@@ -45,10 +45,10 @@ export class TeamRaw extends BaseRaw<ITeam> implements ITeamModel {
 		query?: Filter<ITeam>,
 	): FindCursor<P> | FindCursor<ITeam> {
 		if (options === undefined) {
-			return this.find({ _id: { $in: ids }, ...query });
+			return this.find({ ...query, _id: { $in: ids } });
 		}
 
-		return this.find({ _id: { $in: ids }, ...query }, options);
+		return this.find({ ...query, _id: { $in: ids } }, options);
 	}
 
 	findByIdsPaginated(
@@ -57,10 +57,10 @@ export class TeamRaw extends BaseRaw<ITeam> implements ITeamModel {
 		query?: Filter<ITeam>,
 	): FindPaginated<FindCursor<ITeam>> {
 		if (options === undefined) {
-			return this.findPaginated({ _id: { $in: ids }, ...query });
+			return this.findPaginated({ ...query, _id: { $in: ids } });
 		}
 
-		return this.findPaginated({ _id: { $in: ids }, ...query }, options);
+		return this.findPaginated({ ...query, _id: { $in: ids } }, options);
 	}
 
 	findByIdsAndType(ids: Array<string>, type: TEAM_TYPE): FindCursor<ITeam>;
diff --git a/apps/meteor/tests/data/api-data.js b/apps/meteor/tests/data/api-data.js
index cab98c41eb1..bb9cf3458b8 100644
--- a/apps/meteor/tests/data/api-data.js
+++ b/apps/meteor/tests/data/api-data.js
@@ -46,6 +46,10 @@ export function methodCall(methodName) {
 	return api(`method.call/${methodName}`);
 }
 
+export function methodCallAnon(methodName) {
+	return api(`method.callAnon/${methodName}`);
+}
+
 export function log(res) {
 	console.log(res.req.path);
 	console.log({
diff --git a/apps/meteor/tests/end-to-end/api/01-users.js b/apps/meteor/tests/end-to-end/api/01-users.js
index ee53820daa2..07e7661e8c4 100644
--- a/apps/meteor/tests/end-to-end/api/01-users.js
+++ b/apps/meteor/tests/end-to-end/api/01-users.js
@@ -793,6 +793,10 @@ describe('[Users]', function () {
 		let deactivatedUser;
 		let user2;
 		let user2Credentials;
+		let user3;
+		let user3Credentials;
+		let group;
+		let inviteToken;
 
 		before(async () => {
 			const username = `deactivated_${Date.now()}${apiUsername}`;
@@ -849,14 +853,44 @@ describe('[Users]', function () {
 		before(async () => {
 			user2 = await createUser({ joinDefaultChannels: false });
 			user2Credentials = await login(user2.username, password);
+			user3 = await createUser({ joinDefaultChannels: false });
+			user3Credentials = await login(user3.username, password);
 		});
 
+		before('Create a group', async () => {
+			group = (
+				await createRoom({
+					type: 'p',
+					name: `group.test.${Date.now()}-${Math.random()}`,
+				})
+			).body.group;
+		});
+
+		before('Create invite link', async () => {
+			inviteToken = (
+				await request.post(api('findOrCreateInvite')).set(credentials).send({
+					rid: group._id,
+					days: 0,
+					maxUses: 0,
+				})
+			).body._id;
+		});
+
+		after('Remove invite link', async () =>
+			request
+				.delete(api(`removeInvite/${inviteToken}`))
+				.set(credentials)
+				.send(),
+		);
+
 		after(() =>
 			Promise.all([
 				clearCustomFields(),
 				deleteUser(deactivatedUser),
 				deleteUser(user),
 				deleteUser(user2),
+				deleteUser(user3),
+				deleteRoom({ type: 'p', roomId: group._id }),
 				updatePermission('view-outside-room', ['admin', 'owner', 'moderator', 'user']),
 				updateSetting('API_Apply_permission_view-outside-room_on_users-list', false),
 			]),
@@ -979,6 +1013,70 @@ describe('[Users]', function () {
 
 			await request.get(api('users.list')).set(user2Credentials).expect('Content-Type', 'application/json').expect(403);
 		});
+
+		it('should exclude inviteToken in the user item for privileged users even when fields={inviteToken:1} is specified', async () => {
+			await request
+				.post(api('useInviteToken'))
+				.set(user2Credentials)
+				.send({ token: inviteToken })
+				.expect(200)
+				.expect('Content-Type', 'application/json')
+				.expect((res) => {
+					expect(res.body).to.have.property('success', true);
+					expect(res.body).to.have.property('room');
+					expect(res.body.room).to.have.property('rid', group._id);
+				});
+
+			await request
+				.get(api('users.list'))
+				.set(credentials)
+				.expect('Content-Type', 'application/json')
+				.query({
+					fields: JSON.stringify({ inviteToken: 1 }),
+					sort: JSON.stringify({ inviteToken: -1 }),
+					count: 100,
+				})
+				.expect(200)
+				.expect((res) => {
+					expect(res.body).to.have.property('success', true);
+					expect(res.body).to.have.property('users');
+					res.body.users.forEach((user) => {
+						expect(user).to.not.have.property('inviteToken');
+					});
+				});
+		});
+
+		it('should exclude inviteToken in the user item for normal users even when fields={inviteToken:1} is specified', async () => {
+			await updateSetting('API_Apply_permission_view-outside-room_on_users-list', false);
+			await request
+				.post(api('useInviteToken'))
+				.set(user3Credentials)
+				.send({ token: inviteToken })
+				.expect(200)
+				.expect((res) => {
+					expect(res.body).to.have.property('success', true);
+					expect(res.body).to.have.property('room');
+					expect(res.body.room).to.have.property('rid', group._id);
+				});
+
+			await request
+				.get(api('users.list'))
+				.set(user3Credentials)
+				.expect('Content-Type', 'application/json')
+				.query({
+					fields: JSON.stringify({ inviteToken: 1 }),
+					sort: JSON.stringify({ inviteToken: -1 }),
+					count: 100,
+				})
+				.expect(200)
+				.expect((res) => {
+					expect(res.body).to.have.property('success', true);
+					expect(res.body).to.have.property('users');
+					res.body.users.forEach((user) => {
+						expect(user).to.not.have.property('inviteToken');
+					});
+				});
+		});
 	});
 
 	describe('Avatars', () => {
diff --git a/apps/meteor/tests/end-to-end/api/16-commands.js b/apps/meteor/tests/end-to-end/api/16-commands.js
index c0781167b67..3470bcffe82 100644
--- a/apps/meteor/tests/end-to-end/api/16-commands.js
+++ b/apps/meteor/tests/end-to-end/api/16-commands.js
@@ -1,3 +1,4 @@
+import { Random } from '@rocket.chat/random';
 import { expect } from 'chai';
 import { before, describe, it, after } from 'mocha';
 
@@ -390,4 +391,133 @@ describe('[Commands]', function () {
 			});
 		});
 	});
+
+	describe('Command "invite-all-from"', function () {
+		let group;
+		let group1;
+		let channel;
+		let user1;
+		let user2;
+		let user1Credentials;
+		let user2Credentials;
+
+		this.beforeAll(async () => {
+			user1 = await createUser();
+			user2 = await createUser();
+
+			[user1Credentials, user2Credentials] = await Promise.all([login(user1.username, password), login(user2.username, password)]);
+		});
+
+		this.beforeAll(async () => {
+			const [response1, response2, response3] = await Promise.all([
+				createRoom({ type: 'p', name: `room1-${Date.now()}.${Random.id()}`, credentials: user1Credentials }),
+				createRoom({ type: 'c', name: `room2-${Date.now()}.${Random.id()}`, credentials: user2Credentials }),
+				createRoom({ type: 'p', name: `room3-${Date.now()}.${Random.id()}` }),
+			]);
+			group = response1.body.group;
+			channel = response2.body.channel;
+			group1 = response3.body.group;
+		});
+
+		this.afterAll(async () => {
+			await Promise.all([
+				deleteRoom({ type: 'p', roomId: group._id }),
+				deleteRoom({ type: 'c', roomId: channel._id }),
+				deleteRoom({ type: 'p', roomId: group1._id }),
+			]);
+			await Promise.all([deleteUser(user1), deleteUser(user2)]);
+		});
+
+		it('should not add users from group which is not accessible by current user', async () => {
+			await request
+				.post(api('commands.run'))
+				.set(user2Credentials)
+				.send({
+					roomId: channel._id,
+					command: 'invite-all-from',
+					params: `#${group.name}`,
+					msg: {
+						_id: Random.id(),
+						rid: channel._id,
+						msg: `invite-all-from #${group.name}`,
+					},
+					triggerId: Random.id(),
+				})
+				.expect(200)
+				.expect(async (res) => {
+					expect(res.body).to.have.a.property('success', true);
+				});
+
+			await request
+				.get(api('channels.members'))
+				.query({ roomId: channel._id })
+				.set(user2Credentials)
+				.expect(200)
+				.expect((res) => {
+					const isUser1Added = res.body.members.some((member) => member.username === user1.username);
+					expect(isUser1Added).to.be.false;
+				});
+		});
+
+		it('should not add users to a room that is not accessible by the current user', async () => {
+			await request
+				.post(api('commands.run'))
+				.set(user1Credentials)
+				.send({
+					roomId: group1._id,
+					command: 'invite-all-from',
+					params: `#${group.name}`,
+					msg: {
+						_id: Random.id(),
+						rid: group1._id,
+						msg: `invite-all-from #${group.name}`,
+					},
+					triggerId: Random.id(),
+				})
+				.expect(403)
+				.expect((res) => {
+					expect(res.body).to.have.a.property('error', 'unauthorized');
+				});
+		});
+
+		it('should add users from group which is accessible by current user', async () => {
+			await request
+				.post(api('groups.invite'))
+				.set(user1Credentials)
+				.send({
+					roomId: group._id,
+					userId: user2._id,
+				})
+				.expect(200);
+
+			await request
+				.post(api('commands.run'))
+				.set(user2Credentials)
+				.send({
+					roomId: channel._id,
+					command: 'invite-all-from',
+					params: `#${group.name}`,
+					msg: {
+						_id: Random.id(),
+						rid: channel._id,
+						msg: `invite-all-from #${group.name}`,
+					},
+					triggerId: Random.id(),
+				})
+				.expect(200)
+				.expect(async (res) => {
+					expect(res.body).to.have.a.property('success', true);
+				});
+
+			await request
+				.get(api('channels.members'))
+				.set(user2Credentials)
+				.query({ roomId: channel._id })
+				.expect(200)
+				.expect((res) => {
+					const isUser1Added = res.body.members.some((member) => member.username === user1.username);
+					expect(isUser1Added).to.be.true;
+				});
+		});
+	});
 });
diff --git a/apps/meteor/tests/end-to-end/api/25-teams.js b/apps/meteor/tests/end-to-end/api/25-teams.js
index 6fc9899e403..d016a699fa6 100644
--- a/apps/meteor/tests/end-to-end/api/25-teams.js
+++ b/apps/meteor/tests/end-to-end/api/25-teams.js
@@ -493,18 +493,51 @@ describe('[Teams]', () => {
 
 	describe('/teams.list', () => {
 		const teamName = `test-team-list-${Date.now()}`;
-		before('Create test team', (done) => {
-			request
-				.post(api('teams.create'))
-				.set(credentials)
-				.send({
-					name: teamName,
+		let testUser1;
+		let testUser1Credentials;
+		let testTeamAdmin;
+		let testTeam1;
+
+		before('Create test users', async () => {
+			testUser1 = await createUser();
+		});
+
+		before('login test users', async () => {
+			testUser1Credentials = await login(testUser1.username, password);
+		});
+
+		before('Create test team', async () => {
+			await request.post(api('teams.create')).set(credentials).send({
+				name: teamName,
+				type: 0,
+			});
+
+			const team1Name = `test-team-1-${Date.now()}`;
+			const teamAdminName = `test-team-admin-${Date.now()}`;
+
+			testTeam1 = (
+				await request.post(api('teams.create')).set(testUser1Credentials).send({
+					name: team1Name,
 					type: 0,
 				})
-				.end(done);
+			).body.team;
+			testTeamAdmin = (
+				await request.post(api('teams.create')).set(credentials).send({
+					name: teamAdminName,
+					type: 0,
+				})
+			).body.team;
 		});
 
-		after(() => deleteTeam(credentials, teamName));
+		after(() =>
+			Promise.all([
+				deleteTeam(credentials, teamName),
+				deleteTeam(testUser1Credentials, testTeam1.name),
+				deleteTeam(credentials, testTeamAdmin.name),
+			]),
+		);
+
+		after('delete test users', () => deleteUser(testUser1));
 
 		it('should list all teams', (done) => {
 			request
@@ -518,7 +551,7 @@ describe('[Teams]', () => {
 					expect(res.body).to.have.property('offset', 0);
 					expect(res.body).to.have.property('total');
 					expect(res.body).to.have.property('teams');
-					expect(res.body.teams.length).equal(1);
+					expect(res.body.teams.length).to.be.gte(1);
 					expect(res.body.teams[0]).to.include.property('_id');
 					expect(res.body.teams[0]).to.include.property('_updatedAt');
 					expect(res.body.teams[0]).to.include.property('name');
@@ -533,6 +566,54 @@ describe('[Teams]', () => {
 				})
 				.end(done);
 		});
+
+		it("should prevent users from accessing unrelated teams via 'query' parameter", () => {
+			return request
+				.get(api('teams.list'))
+				.set(testUser1Credentials)
+				.query({
+					query: JSON.stringify({ _id: { $regex: '.*' } }),
+				})
+				.expect('Content-Type', 'application/json')
+				.expect(200)
+				.expect((res) => {
+					expect(res.body.teams.length).to.be.gte(1);
+					expect(res.body.teams)
+						.to.be.an('array')
+						.and.to.satisfy(
+							(teams) => teams.every((team) => team.createdBy._id === testUser1._id),
+							`Expected only user's own teams to be returned, but found unowned teams.\n${JSON.stringify(
+								res.body.teams.filter((team) => team.createdBy._id !== testUser1._id),
+								null,
+								2,
+							)}`,
+						);
+				});
+		});
+
+		it("should prevent admins from accessing unrelated teams via 'query' parameter", () => {
+			return request
+				.get(api('teams.list'))
+				.set(credentials)
+				.query({
+					query: JSON.stringify({ _id: { $regex: '.*' } }),
+				})
+				.expect('Content-Type', 'application/json')
+				.expect(200)
+				.expect((res) => {
+					expect(res.body.teams.length).to.be.gte(1);
+					expect(res.body.teams)
+						.to.be.an('array')
+						.and.to.satisfy(
+							(teams) => teams.every((team) => team.createdBy._id === credentials['X-User-Id']),
+							`Expected only admin's own teams to be returned, but found unowned teams.\n${JSON.stringify(
+								res.body.teams.filter((team) => team.createdBy._id !== credentials['X-User-Id']),
+								null,
+								2,
+							)}`,
+						);
+				});
+		});
 	});
 
 	describe('/teams.updateMember', () => {
diff --git a/apps/meteor/tests/end-to-end/api/livechat/20-messages.ts b/apps/meteor/tests/end-to-end/api/livechat/20-messages.ts
index eae12533ade..ead41ffafd4 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/20-messages.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/20-messages.ts
@@ -1,8 +1,10 @@
 import { faker } from '@faker-js/faker';
+import type { ILivechatAgent, ILivechatVisitor, IOmnichannelRoom, IRoom } from '@rocket.chat/core-typings';
 import { expect } from 'chai';
-import { before, describe, it } from 'mocha';
+import { before, describe, it, after } from 'mocha';
 
-import { getCredentials } from '../../../data/api-data';
+import { api, getCredentials, request } from '../../../data/api-data';
+import { sendSimpleMessage } from '../../../data/chat.helper';
 import {
 	sendMessage,
 	startANewLivechatRoomAndTakeIt,
@@ -10,18 +12,24 @@ import {
 	createAgent,
 	makeAgentAvailable,
 	uploadFile,
+	closeOmnichannelRoom,
 } from '../../../data/livechat/rooms';
+import { removeAgent } from '../../../data/livechat/users';
 import { updateSetting } from '../../../data/permissions.helper';
+import { createRoom, deleteRoom } from '../../../data/rooms.helper';
 
 describe('LIVECHAT - messages', () => {
+	let agent: ILivechatAgent;
 	before((done) => getCredentials(done));
 
 	before(async () => {
-		await createAgent();
+		agent = await createAgent();
 		await makeAgentAvailable();
 		await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
 	});
 
+	after(() => Promise.all([updateSetting('Livechat_Routing_Method', 'Auto_Selection'), removeAgent(agent._id)]));
+
 	describe('Quote message feature for visitors', () => {
 		it('it should verify if visitor can quote message', async () => {
 			const {
@@ -66,4 +74,50 @@ describe('LIVECHAT - messages', () => {
 			expect(imgMessage).to.have.property('file').that.deep.equal(imgMessage?.files?.[0]);
 		});
 	});
+
+	describe('Livechat Messages', async () => {
+		let room: IOmnichannelRoom;
+		let privateRoom: IRoom;
+		let visitor: ILivechatVisitor;
+
+		before(async () => {
+			await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
+
+			const data = await startANewLivechatRoomAndTakeIt();
+			visitor = data.visitor;
+			room = data.room;
+
+			const response = await createRoom({ type: 'p', name: `private-room-${Math.random()}` } as any);
+			privateRoom = response.body.group;
+		});
+
+		after(() => Promise.all([closeOmnichannelRoom(room._id), deleteRoom({ roomId: privateRoom._id, type: 'p' })]));
+
+		it('should not allow fetching arbitrary messages from another channel', async () => {
+			const response = await sendSimpleMessage({ roomId: privateRoom._id } as any);
+			const { message } = response.body;
+
+			await request
+				.get(api(`livechat/message/${message._id}`))
+				.query({ token: visitor.token, rid: room._id })
+				.send()
+				.expect(400)
+				.expect((res) => {
+					expect(res.body.error).to.be.equal('invalid-message');
+				});
+		});
+
+		it('should allow fetching messages using their _id and roomId', async () => {
+			const message = await sendMessage(room._id, 'Hello from visitor', visitor.token);
+
+			await request
+				.get(api(`livechat/message/${message._id}`))
+				.query({ token: visitor.token, rid: room._id })
+				.send()
+				.expect(200)
+				.expect((res) => {
+					expect(res.body.message._id).to.be.equal(message._id);
+				});
+		});
+	});
 });
diff --git a/apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts b/apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts
new file mode 100644
index 00000000000..f251d7ebe92
--- /dev/null
+++ b/apps/meteor/tests/end-to-end/api/livechat/methods/loadHistory.ts
@@ -0,0 +1,61 @@
+import type { ILivechatAgent } from '@rocket.chat/core-typings';
+import { expect } from 'chai';
+import { before, describe, it, after } from 'mocha';
+import type { Response } from 'supertest';
+
+import { getCredentials, request, methodCallAnon, credentials } from '../../../../data/api-data';
+import { createAgent, makeAgentAvailable, sendMessage, startANewLivechatRoomAndTakeIt } from '../../../../data/livechat/rooms';
+import { removeAgent } from '../../../../data/livechat/users';
+import { updateSetting } from '../../../../data/permissions.helper';
+import { adminUsername } from '../../../../data/user';
+
+describe('livechat:loadHistory', function () {
+	this.retries(0);
+	let agent: ILivechatAgent;
+
+	before((done) => getCredentials(done));
+
+	before(async () => {
+		await updateSetting('Livechat_enabled', true);
+		agent = await createAgent(adminUsername);
+		await makeAgentAvailable(credentials);
+	});
+
+	after('remove agent', async () => {
+		await removeAgent(agent._id);
+	});
+
+	describe('loadHistory', async () => {
+		it('prevent getting unrelated message history using regex on rid param', async () => {
+			const {
+				room: { _id: roomId },
+				visitor: { token },
+			} = await startANewLivechatRoomAndTakeIt();
+
+			await sendMessage(roomId, 'Hello from visitor', token);
+
+			await request
+				.post(methodCallAnon('livechat:loadHistory'))
+				.send({
+					message: JSON.stringify({
+						msg: 'method',
+						id: 'id2',
+						method: 'livechat:loadHistory',
+						params: [
+							{
+								token,
+								rid: { $regex: '.*' },
+							},
+						],
+					}),
+				})
+				.expect(200)
+				.expect((res: Response) => {
+					expect(res.body).to.have.property('success', true);
+					const parsedBody = JSON.parse(res.body.message);
+					expect(parsedBody).to.have.property('error');
+					expect(parsedBody).to.not.have.property('result');
+				});
+		});
+	});
+});
diff --git a/apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts b/apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts
new file mode 100644
index 00000000000..be6fee9144d
--- /dev/null
+++ b/apps/meteor/tests/end-to-end/api/livechat/methods/loginByToken.ts
@@ -0,0 +1,66 @@
+import type { ILivechatAgent, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings';
+import { expect } from 'chai';
+import { before, describe, it, after } from 'mocha';
+import type { Response } from 'supertest';
+
+import { getCredentials, request, methodCallAnon, credentials } from '../../../../data/api-data';
+import {
+	closeOmnichannelRoom,
+	createAgent,
+	makeAgentAvailable,
+	sendMessage,
+	startANewLivechatRoomAndTakeIt,
+} from '../../../../data/livechat/rooms';
+import { removeAgent } from '../../../../data/livechat/users';
+import { updateSetting } from '../../../../data/permissions.helper';
+import { adminUsername } from '../../../../data/user';
+
+describe('livechat:loginByTokens', function () {
+	let visitor: ILivechatVisitor;
+	let agent: ILivechatAgent;
+	let room: IOmnichannelRoom;
+
+	this.retries(0);
+
+	before((done) => getCredentials(done));
+
+	before(async () => {
+		await updateSetting('Livechat_enabled', true);
+		agent = await createAgent(adminUsername);
+		await makeAgentAvailable(credentials);
+	});
+
+	before('open livechat room', async () => {
+		const data = await startANewLivechatRoomAndTakeIt();
+		visitor = data.visitor;
+		room = data.room;
+		await sendMessage(data.room._id, 'Hello from visitor!', visitor.token);
+	});
+
+	after('remove agent and close room', async () => {
+		await closeOmnichannelRoom(room._id);
+		await removeAgent(agent._id);
+	});
+
+	describe('loginByTokens', async () => {
+		it('prevent getting arbitrary visitor id using regex in params', async () => {
+			await request
+				.post(methodCallAnon('livechat:loginByToken'))
+				.send({
+					message: JSON.stringify({
+						msg: 'method',
+						id: 'id1',
+						method: 'livechat:loginByToken',
+						params: [{ $regex: `.*` }],
+					}),
+				})
+				.expect(200)
+				.expect((res: Response) => {
+					expect(res.body).to.have.property('success', true);
+					const parsedBody = JSON.parse(res.body.message);
+					expect(parsedBody).to.have.property('error');
+					expect(parsedBody).to.not.have.property('result');
+				});
+		});
+	});
+});
-- 
GitLab