diff --git a/.changeset/neat-trams-juggle.md b/.changeset/neat-trams-juggle.md new file mode 100644 index 0000000000000..56f327f68cc0c --- /dev/null +++ b/.changeset/neat-trams-juggle.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Ensures the Meteor method for translateMessage validates access and types diff --git a/apps/meteor/app/autotranslate/server/methods/translateMessage.ts b/apps/meteor/app/autotranslate/server/methods/translateMessage.ts index 7c8741782647c..1c68073948f6e 100644 --- a/apps/meteor/app/autotranslate/server/methods/translateMessage.ts +++ b/apps/meteor/app/autotranslate/server/methods/translateMessage.ts @@ -1,7 +1,10 @@ import type { IMessage } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { Messages, Rooms } from '@rocket.chat/models'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { canAccessRoomAsync } from '../../../authorization/server'; import { translateMessage } from '../functions/translateMessage'; declare module '@rocket.chat/ddp-client' { @@ -13,6 +16,22 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async 'autoTranslate.translateMessage'(message, targetLanguage) { - return translateMessage(targetLanguage, message); + const userId = Meteor.userId(); + if (!userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'autoTranslate.translateMessage', + }); + } + check(message?._id, String); + check(targetLanguage, String); + const msg = await Messages.findOneById(message._id); + if (!msg) { + throw new Meteor.Error('error-message-not-found', 'Message not found'); + } + const room = await Rooms.findOneById(msg.rid); + if (!room || !(await canAccessRoomAsync(room, { _id: userId }))) { + throw new Meteor.Error('error-not-allowed', 'Not allowed'); + } + return translateMessage(targetLanguage, msg); }, }); diff --git a/apps/meteor/tests/end-to-end/api/autotranslate.ts b/apps/meteor/tests/end-to-end/api/autotranslate.ts index 3b5e03e8016f5..ffe873b171a72 100644 --- a/apps/meteor/tests/end-to-end/api/autotranslate.ts +++ b/apps/meteor/tests/end-to-end/api/autotranslate.ts @@ -3,7 +3,7 @@ import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { before, describe, after, it } from 'mocha'; -import { getCredentials, api, request, credentials } from '../../data/api-data'; +import { getCredentials, api, request, credentials, methodCall } from '../../data/api-data'; import { sendSimpleMessage } from '../../data/chat.helper'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom, deleteRoom } from '../../data/rooms.helper'; @@ -376,6 +376,94 @@ describe('AutoTranslate', () => { }); }); + describe('[autoTranslate.translateMessage method]', () => { + let userA: TestUser; + let userB: TestUser; + let credA: Credentials; + let credB: Credentials; + let privateRoom: IRoom; + let privateMessage: IMessage; + + before(async () => { + await updateSetting('AutoTranslate_Enabled', true); + + userA = await createUser(); + userB = await createUser(); + + credA = await login(userA.username, password); + credB = await login(userB.username, password); + + privateRoom = ( + await createRoom({ + type: 'p', + name: `test-autotranslate-method-${Date.now()}`, + credentials: credA, + }) + ).body.group; + + const msgRes = await sendSimpleMessage({ + roomId: privateRoom._id, + text: 'Isso é um teste', + userCredentials: credA, + }); + privateMessage = msgRes.body.message; + }); + + after(async () => { + await Promise.all([ + updateSetting('AutoTranslate_Enabled', false), + deleteUser(userA), + deleteUser(userB), + deleteRoom({ type: 'p', roomId: privateRoom._id }), + ]); + }); + + it('should fail when messageId is not a string', (done) => { + void request + .post(methodCall('autoTranslate.translateMessage')) + .set(credA) + .send({ + message: JSON.stringify({ + msg: 'method', + id: 'id', + method: 'autoTranslate.translateMessage', + params: [{ _id: { $gt: '' } }, 'en'], + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + const parsedBody = JSON.parse(res.body.message); + expect(parsedBody).to.have.a.property('error'); + }) + .end(done); + }); + + it('should return error-not-allowed when the caller is not a member of the room', (done) => { + void request + .post(methodCall('autoTranslate.translateMessage')) + .set(credB) + .send({ + message: JSON.stringify({ + msg: 'method', + id: 'id', + method: 'autoTranslate.translateMessage', + params: [{ _id: privateMessage._id }, 'en'], + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + const parsedBody = JSON.parse(res.body.message); + expect(parsedBody).to.have.a.property('error'); + expect(parsedBody.error).to.have.a.property('error', 'error-not-allowed'); + }) + .end(done); + }); + }); + describe('Autoenable setting', () => { let userA: TestUser; let userB: TestUser;