Skip to content

Commit

Permalink
fix: Handle encrypted pinned messages (#32380)
Browse files Browse the repository at this point in the history
Co-authored-by: Hugo Costa <[email protected]>
Co-authored-by: Guilherme Gazzo <[email protected]>
  • Loading branch information
3 people authored Jun 22, 2024
1 parent 161813c commit 1240c87
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/forty-bikes-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/core-typings': patch
'@rocket.chat/meteor': patch
---

Decrypt pinned encrypted messages in the chat and pinned messages contextual bar.
27 changes: 26 additions & 1 deletion apps/meteor/app/e2e/client/rocketchat.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import QueryString from 'querystring';
import URL from 'url';

import type { IE2EEMessage, IMessage, IRoom, ISubscription, IUploadWithUser } from '@rocket.chat/core-typings';
import type { IE2EEMessage, IMessage, IRoom, ISubscription, IUploadWithUser, MessageAttachment } from '@rocket.chat/core-typings';
import { isE2EEMessage } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';
import EJSON from 'ejson';
Expand Down Expand Up @@ -550,6 +550,31 @@ class E2E extends Emitter {
return decryptedMessageWithQuote;
}

async decryptPinnedMessage(message: IMessage) {
const pinnedMessage = message?.attachments?.[0]?.text;

if (!pinnedMessage) {
return message;
}

const e2eRoom = await this.getInstanceByRoomId(message.rid);

if (!e2eRoom) {
return message;
}

const data = await e2eRoom.decrypt(pinnedMessage);

if (!data) {
return message;
}

const decryptedPinnedMessage = { ...message } as IMessage & { attachments: MessageAttachment[] };
decryptedPinnedMessage.attachments[0].text = data.text;

return decryptedPinnedMessage;
}

async decryptPendingMessages(): Promise<void> {
return Messages.find({ t: 'e2e', e2e: 'pending' }).forEach(async ({ _id, ...msg }: IMessage) => {
Messages.update({ _id }, await this.decryptMessage(msg as IE2EEMessage));
Expand Down
4 changes: 3 additions & 1 deletion apps/meteor/app/message-pin/server/pinMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ Meteor.methods<ServerMethods>({
// App IPostMessagePinned event hook
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned);

const msgId = await Message.saveSystemMessage('message_pinned', originalMessage.rid, '', me, {
const pinMessageType = originalMessage.t === 'e2e' ? 'message_pinned_e2e' : 'message_pinned';

const msgId = await Message.saveSystemMessage(pinMessageType, originalMessage.rid, '', me, {
attachments: [
{
text: originalMessage.msg,
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/components/message/StatusIndicators.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IMessage, ITranslatedMessage } from '@rocket.chat/core-typings';
import { isEditedMessage, isE2EEMessage, isOTRMessage, isOTRAckMessage } from '@rocket.chat/core-typings';
import { isEditedMessage, isE2EEMessage, isOTRMessage, isOTRAckMessage, isE2EEPinnedMessage } from '@rocket.chat/core-typings';
import { MessageStatusIndicator, MessageStatusIndicatorItem } from '@rocket.chat/fuselage';
import { useUserId, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
Expand All @@ -17,7 +17,7 @@ const StatusIndicators = ({ message }: StatusIndicatorsProps): ReactElement => {
const starred = useShowStarred({ message });
const following = useShowFollowing({ message });

const isEncryptedMessage = isE2EEMessage(message);
const isEncryptedMessage = isE2EEMessage(message) || isE2EEPinnedMessage(message);
const isOtrMessage = isOTRMessage(message) || isOTRAckMessage(message);

const uid = useUserId();
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/client/startup/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IMessage, ISubscription } from '@rocket.chat/core-typings';
import { isE2EEPinnedMessage } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';

Expand Down Expand Up @@ -125,6 +126,11 @@ Meteor.startup(() => {
if (!e2eRoom?.shouldConvertReceivedMessages()) {
return msg;
}

if (isE2EEPinnedMessage(msg)) {
return e2e.decryptPinnedMessage(msg);
}

return e2e.decryptMessage(msg);
});

Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/client/startup/messageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ Meteor.startup(() => {
system: true,
message: 'Pinned_a_message',
});

MessageTypes.registerType({
id: 'message_pinned_e2e',
system: true,
message: 'Pinned_a_message',
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import React from 'react';

import { onClientMessageReceived } from '../../../lib/onClientMessageReceived';
import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
import { useRoom } from '../contexts/RoomContext';
import MessageListTab from './MessageListTab';
Expand All @@ -25,7 +26,7 @@ const PinnedMessagesTab = (): ReactElement => {
messages.push(...result.messages.map(mapMessageFromApi));
}

return messages;
return Promise.all(messages.map(onClientMessageReceived));
});

const t = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import { onClientMessageReceived } from '../../../lib/onClientMessageReceived';
import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
import { useRoom } from '../contexts/RoomContext';
import MessageListTab from './MessageListTab';
Expand All @@ -24,7 +25,7 @@ const StarredMessagesTab = () => {
messages.push(...result.messages.map(mapMessageFromApi));
}

return messages;
return Promise.all(messages.map(onClientMessageReceived));
});

const t = useTranslation();
Expand Down
45 changes: 45 additions & 0 deletions apps/meteor/tests/e2e/e2e-encryption.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,51 @@ test.describe.serial('e2e-encryption', () => {
await expect(sidebarChannel.locator('span')).toContainText(encriptedMessage1);
});

test('expect create a private encrypted channel and pin/star an encrypted message', async ({ page }) => {
const channelName = faker.string.uuid();

await poHomeChannel.sidenav.createEncryptedChannel(channelName);

await expect(page).toHaveURL(`/group/${channelName}`);

await poHomeChannel.dismissToast();

await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();

await poHomeChannel.content.sendMessage('This message should be pinned and stared.');

await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This message should be pinned and stared.');
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();

await poHomeChannel.content.openLastMessageMenu();
await page.locator('role=menuitem[name="Star"]').click();

await expect(poHomeChannel.toastSuccess).toBeVisible();
await poHomeChannel.dismissToast();

await poHomeChannel.content.openLastMessageMenu();
await page.locator('role=menuitem[name="Pin"]').click();
await page.locator('#modal-root >> button:has-text("Yes, pin message")').click();

await poHomeChannel.tabs.kebab.click();
await poHomeChannel.tabs.btnPinnedMessagesList.click();

await expect(page.getByRole('dialog', { name: 'Pinned Messages' })).toBeVisible();
await expect(page.getByRole('dialog', { name: 'Pinned Messages' }).locator('[data-qa-type="message"]').last()).toContainText(
'This message should be pinned and stared.',
);

await poHomeChannel.btnContextualbarClose.click();

await poHomeChannel.tabs.kebab.click();
await poHomeChannel.tabs.btnStarredMessageList.click();

await expect(page.getByRole('dialog', { name: 'Starred Messages' })).toBeVisible();
await expect(page.getByRole('dialog', { name: 'Starred Messages' }).locator('[data-qa-type="message"]').last()).toContainText(
'This message should be pinned and stared.',
);
});

test.describe('reset keys', () => {
let anotherClientPage: Page;

Expand Down
8 changes: 8 additions & 0 deletions apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,12 @@ export class HomeFlextab {
get userInfoUsername(): Locator {
return this.page.locator('[data-qa="UserInfoUserName"]');
}

get btnPinnedMessagesList(): Locator {
return this.page.locator('[data-key="pinned-messages"]');
}

get btnStarredMessageList(): Locator {
return this.page.locator('[data-key="starred-messages"]');
}
}
6 changes: 6 additions & 0 deletions packages/core-typings/src/IMessage/IMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export type MessageTypesValues =
| 'command'
| 'videoconf'
| 'message_pinned'
| 'message_pinned_e2e'
| 'new-moderator'
| 'moderator-removed'
| 'new-owner'
Expand Down Expand Up @@ -364,6 +365,10 @@ export type IE2EEMessage = IMessage & {
e2e: 'pending' | 'done';
};

export type IE2EEPinnedMessage = IMessage & {
t: 'message_pinned_e2e';
};

export interface IOTRMessage extends IMessage {
t: 'otr';
otrAck?: string;
Expand All @@ -378,6 +383,7 @@ export type IVideoConfMessage = IMessage & {
};

export const isE2EEMessage = (message: IMessage): message is IE2EEMessage => message.t === 'e2e';
export const isE2EEPinnedMessage = (message: IMessage): message is IE2EEPinnedMessage => message.t === 'message_pinned_e2e';
export const isOTRMessage = (message: IMessage): message is IOTRMessage => message.t === 'otr';
export const isOTRAckMessage = (message: IMessage): message is IOTRAckMessage => message.t === 'otr-ack';
export const isVideoConfMessage = (message: IMessage): message is IVideoConfMessage => message.t === 'videoconf';
Expand Down

0 comments on commit 1240c87

Please sign in to comment.