diff --git a/.changeset/fast-forks-sin.md b/.changeset/fast-forks-sin.md new file mode 100644 index 0000000000000..9611434dc5b24 --- /dev/null +++ b/.changeset/fast-forks-sin.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes crash in end-to-end encrypted rooms when sending a quote or message link referencing a message outside the room. diff --git a/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts b/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts index e1a42f6c792ab..f22dd8fc35c55 100644 --- a/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts +++ b/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts @@ -1,7 +1,16 @@ import QueryString from 'querystring'; import URL from 'url'; -import type { IE2EEMessage, IMessage, IRoom, ISubscription, IUser, IUploadWithUser, MessageAttachment } from '@rocket.chat/core-typings'; +import type { + IE2EEMessage, + IMessage, + IRoom, + ISubscription, + IUser, + IUploadWithUser, + MessageAttachment, + Serialized, +} from '@rocket.chat/core-typings'; import { isE2EEMessage, isEncryptedMessageContent } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { imperativeModal } from '@rocket.chat/ui-client'; @@ -773,8 +782,14 @@ class E2E extends Emitter { return; } - const getQuotedMessage = await sdk.rest.get('/v1/chat.getMessage', { msgId }); - const quotedMessage = getQuotedMessage?.message; + let quotedMessage: Serialized; + try { + const getQuotedMessage = await sdk.rest.get('/v1/chat.getMessage', { msgId }); + quotedMessage = getQuotedMessage?.message; + } catch (error) { + console.error(`Error getting quoted message: ${error}`); + return; + } if (!quotedMessage) { return; @@ -783,7 +798,6 @@ class E2E extends Emitter { const decryptedQuoteMessage = await this.decryptMessage(mapMessageFromApi(quotedMessage)); message.attachments = message.attachments || []; - const useRealName = settings.peek('UI_Use_Real_Name'); const quoteAttachment = createQuoteAttachment( decryptedQuoteMessage, diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts index 7cd6bf8d4fe12..3769d88ea53fd 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts @@ -1,12 +1,15 @@ import { faker } from '@faker-js/faker'; import { setupE2EEPassword } from './setupE2EEPassword'; +import { BASE_URL } from '../config/constants'; import { Users } from '../fixtures/userStates'; import { EncryptedRoomPage } from '../page-objects/encrypted-room'; import { HomeSidenav } from '../page-objects/fragments'; import { FileUploadModal } from '../page-objects/fragments/file-upload-modal'; import { LoginPage } from '../page-objects/login'; +import { createTargetGroupAndReturnFullRoom, deleteChannel, deleteRoom } from '../utils'; import { preserveSettings } from '../utils/preserveSettings'; +import { sendMessageFromUser } from '../utils/sendMessage'; import { test, expect } from '../utils/test'; const settingsList = ['E2E_Enable', 'E2E_Allow_Unencrypted_Messages']; @@ -144,4 +147,58 @@ test.describe('E2EE Encryption and Decryption - Basic Features', () => { await expect(encryptedRoomPage.lastMessage.fileUploadName).toContainText(fileName); await expect(encryptedRoomPage.lastMessage.body).toHaveText(fileDescription); }); + + test.describe('E2EE Quotes', () => { + let targetRoomId: string; + let targetChannelName: string; + + test.afterAll(async ({ api }) => { + await deleteRoom(api, targetRoomId); + await deleteChannel(api, targetChannelName); + }); + + test('expect to not crash and not show quote message for a message_link which is not accessible to the user', async ({ + page, + request, + api, + }) => { + const encryptedRoomPage = new EncryptedRoomPage(page); + const sidenav = new HomeSidenav(page); + targetChannelName = faker.string.uuid(); + + await sidenav.createEncryptedChannel(targetChannelName); + + await expect(page).toHaveURL(`/group/${targetChannelName}`); + await expect(encryptedRoomPage.encryptedIcon).toBeVisible(); + await expect(encryptedRoomPage.encryptionNotReadyIndicator).not.toBeVisible(); + + await encryptedRoomPage.sendMessage('First encrypted message.'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.body).toHaveText('First encrypted message.'); + + // create a private group for user2 + const { group: user1Channel } = await createTargetGroupAndReturnFullRoom(api, { + excludeSelf: true, + members: [Users.user2.data._id], + }); + targetRoomId = user1Channel._id; + + // send a message to the private group, which is not accessible to the main user + const sentMessage = (await sendMessageFromUser(request, Users.user2, targetRoomId, 'This is a test message.')).message; + + const messageLink = `${BASE_URL}/group/${user1Channel.name}?msg=${sentMessage._id}`; + + await encryptedRoomPage.sendMessage(`This is a message with message link - ${messageLink}`); + + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.body).toContainText(`This is a message with message link - ${messageLink}`); + await expect(encryptedRoomPage.lastNthMessage(1).body).toContainText('First encrypted message.'); + + await page.reload(); + + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.body).toContainText(`This is a message with message link - ${messageLink}`); + await expect(encryptedRoomPage.lastNthMessage(1).body).toContainText('First encrypted message.'); + }); + }); }); diff --git a/apps/meteor/tests/e2e/utils/create-target-channel.ts b/apps/meteor/tests/e2e/utils/create-target-channel.ts index ae906bd1d167b..90220f04c9728 100644 --- a/apps/meteor/tests/e2e/utils/create-target-channel.ts +++ b/apps/meteor/tests/e2e/utils/create-target-channel.ts @@ -116,3 +116,11 @@ export async function createArchivedChannel(api: BaseTest['api']): Promise, +): Promise<{ group: IRoom }> { + const name = faker.string.uuid(); + return (await api.post('/groups.create', { name, ...options })).json(); +} diff --git a/apps/meteor/tests/e2e/utils/sendMessage.ts b/apps/meteor/tests/e2e/utils/sendMessage.ts new file mode 100644 index 0000000000000..a17a5158ff006 --- /dev/null +++ b/apps/meteor/tests/e2e/utils/sendMessage.ts @@ -0,0 +1,19 @@ +import type { APIRequestContext } from 'playwright-core'; + +import { BASE_API_URL } from '../config/constants'; +import type { IUserState } from '../fixtures/userStates'; + +export const sendMessageFromUser = async (request: APIRequestContext, user: IUserState, rid: string, message: string) => { + return request + .post(`${BASE_API_URL}/chat.postMessage`, { + headers: { + 'X-Auth-Token': user.data.loginToken, + 'X-User-Id': user.data._id, + }, + data: { + roomId: rid, + text: message, + }, + }) + .then((response) => response.json()); +};