fix: Handle encrypted pinned messages (#32380)

Co-authored-by: Hugo Costa <hugocarreiracosta@gmail.com>
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/32197/head^2
Yash Rajpal 2 years ago committed by GitHub
parent 161813c8f0
commit 1240c874a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/forty-bikes-check.md
  2. 27
      apps/meteor/app/e2e/client/rocketchat.e2e.ts
  3. 4
      apps/meteor/app/message-pin/server/pinMessage.ts
  4. 4
      apps/meteor/client/components/message/StatusIndicators.tsx
  5. 6
      apps/meteor/client/startup/e2e.ts
  6. 6
      apps/meteor/client/startup/messageTypes.ts
  7. 3
      apps/meteor/client/views/room/contextualBar/PinnedMessagesTab.tsx
  8. 3
      apps/meteor/client/views/room/contextualBar/StarredMessagesTab.tsx
  9. 45
      apps/meteor/tests/e2e/e2e-encryption.spec.ts
  10. 8
      apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts
  11. 6
      packages/core-typings/src/IMessage/IMessage.ts

@ -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.

@ -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';
@ -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));

@ -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,

@ -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';
@ -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();

@ -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';
@ -125,6 +126,11 @@ Meteor.startup(() => {
if (!e2eRoom?.shouldConvertReceivedMessages()) {
return msg;
}
if (isE2EEPinnedMessage(msg)) {
return e2e.decryptPinnedMessage(msg);
}
return e2e.decryptMessage(msg);
});

@ -61,4 +61,10 @@ Meteor.startup(() => {
system: true,
message: 'Pinned_a_message',
});
MessageTypes.registerType({
id: 'message_pinned_e2e',
system: true,
message: 'Pinned_a_message',
});
});

@ -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';
@ -25,7 +26,7 @@ const PinnedMessagesTab = (): ReactElement => {
messages.push(...result.messages.map(mapMessageFromApi));
}
return messages;
return Promise.all(messages.map(onClientMessageReceived));
});
const t = useTranslation();

@ -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';
@ -24,7 +25,7 @@ const StarredMessagesTab = () => {
messages.push(...result.messages.map(mapMessageFromApi));
}
return messages;
return Promise.all(messages.map(onClientMessageReceived));
});
const t = useTranslation();

@ -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;

@ -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"]');
}
}

@ -92,6 +92,7 @@ export type MessageTypesValues =
| 'command'
| 'videoconf'
| 'message_pinned'
| 'message_pinned_e2e'
| 'new-moderator'
| 'moderator-removed'
| 'new-owner'
@ -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;
@ -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';

Loading…
Cancel
Save