Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Disable some message menu items for encrypted messages. #32559

Merged
merged 13 commits into from
Jun 19, 2024
6 changes: 6 additions & 0 deletions .changeset/plenty-buses-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Disable "Reply in direct message", "Copy link" and "Forward message" message menu items for encrypted messages as they don't apply to encrypted messages and also disable apps menu items and show a warning.
3 changes: 2 additions & 1 deletion apps/meteor/app/ui-utils/client/lib/MessageAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type MessageActionContext =

type MessageActionType = 'communication' | 'interaction' | 'duplication' | 'apps' | 'management';

type MessageActionConditionProps = {
export type MessageActionConditionProps = {
message: IMessage;
user: IUser | undefined;
room: IRoom;
Expand Down Expand Up @@ -65,6 +65,7 @@ export type MessageActionConfig = {
) => any;
condition?: (props: MessageActionConditionProps) => Promise<boolean> | boolean;
type?: MessageActionType;
disabled?: (props: MessageActionConditionProps) => boolean;
};

class MessageAction {
Expand Down
11 changes: 10 additions & 1 deletion apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { isE2EEMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';

Expand Down Expand Up @@ -63,6 +63,9 @@ Meteor.startup(async () => {
},
order: 0,
group: 'menu',
disabled({ message }) {
return isE2EEMessage(message);
},
});

MessageAction.addButton({
Expand All @@ -87,6 +90,9 @@ Meteor.startup(async () => {
},
order: 0,
group: 'message',
disabled({ message }) {
return isE2EEMessage(message);
},
});

MessageAction.addButton({
Expand Down Expand Up @@ -139,6 +145,9 @@ Meteor.startup(async () => {
},
order: 5,
group: 'menu',
disabled({ message }) {
return isE2EEMessage(message);
},
});

MessageAction.addButton({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const GenericMenu = ({ title, icon = 'menu', disabled, onAction, ...props }: Gen

const hasIcon = itemsList.some(({ icon }) => icon);
const handleItems = (items: GenericMenuItemProps[]) =>
hasIcon ? items.map((item) => ({ ...item, gap: !item.icon && !item.status })) : items;
hasIcon ? items.map((item) => ({ ...item, gap: item.gap ?? (!item.icon && !item.status) })) : items;

const isMenuEmpty = !(sections && sections.length > 0) && !(items && items.length > 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ export type GenericMenuItemProps = {
disabled?: boolean;
description?: ReactNode;
gap?: boolean;
tooltip?: string;
};

const GenericMenuItem = ({ icon, content, addon, status, gap }: GenericMenuItemProps) => (
const GenericMenuItem = ({ icon, content, addon, status, gap, tooltip }: GenericMenuItemProps) => (
<>
{gap && <MenuItemColumn />}
{icon && <MenuItemIcon name={icon} />}
{status && <MenuItemColumn>{status}</MenuItemColumn>}
{content && <MenuItemContent>{content}</MenuItemContent>}
{content && <MenuItemContent title={tooltip}>{content}</MenuItemContent>}
{addon && <MenuItemInput>{addon}</MenuItemInput>}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { MouseEvent, ReactElement } from 'react';
import React from 'react';

import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction';
import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction';
import GenericMenu from '../../GenericMenu/GenericMenu';
import type { GenericMenuItemProps } from '../../GenericMenu/GenericMenuItem';

Expand All @@ -19,11 +20,13 @@ type MessageActionSection = {
type MessageActionMenuProps = {
onChangeMenuVisibility: (visible: boolean) => void;
options: MessageActionConfigOption[];
context: MessageActionConditionProps;
isMessageEncrypted: boolean;
};

const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMenuProps): ReactElement => {
const MessageActionMenu = ({ options, onChangeMenuVisibility, context, isMessageEncrypted }: MessageActionMenuProps): ReactElement => {
const t = useTranslation();

const id = useUniqueId();
const groupOptions = options
.map((option) => ({
variant: option.color === 'alert' ? 'danger' : '',
Expand All @@ -32,6 +35,9 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMen
content: t(option.label),
onClick: option.action,
type: option.type,
...(option.disabled && { disabled: option?.disabled?.(context) }),
...(option.disabled &&
option?.disabled?.(context) && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }),
}))
.reduce((acc, option) => {
const group = option.type ? option.type : '';
Expand All @@ -44,7 +50,31 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMen
acc.push(newSection);

return acc;
}, [] as unknown as MessageActionSection[]);
}, [] as unknown as MessageActionSection[])
.map((section) => {
if (section.id !== 'apps') {
return section;
}

if (!isMessageEncrypted) {
return section;
}

return {
id: 'apps',
title: t('Apps'),
items: [
{
content: t('Unavailable'),
type: 'apps',
id,
disabled: true,
gap: false,
tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }),
},
],
};
});

return (
<GenericMenu
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useToolbar } from '@react-aria/toolbar';
import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings';
import { isThreadMessage, isRoomFederated, isVideoConfMessage } from '@rocket.chat/core-typings';
import { isThreadMessage, isRoomFederated, isVideoConfMessage, isE2EEMessage } from '@rocket.chat/core-typings';
import { MessageToolbar as FuselageMessageToolbar, MessageToolbarItem } from '@rocket.chat/fuselage';
import { useFeaturePreview } from '@rocket.chat/ui-client';
import { useUser, useSettings, useTranslation, useMethod, useLayoutHiddenActions } from '@rocket.chat/ui-contexts';
Expand Down Expand Up @@ -125,9 +125,14 @@ const MessageToolbar = ({
onClick={(e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })}
key={action.id}
icon={action.icon}
title={t(action.label)}
title={
action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context })
? t('Action_not_available_encrypted_content', { action: t(action.label) })
: t(action.label)
}
data-qa-id={action.label}
data-qa-type='message-action-menu'
disabled={action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context })}
/>
))}
{actionsQueryResult.isSuccess && actionsQueryResult.data.menu.length > 0 && (
Expand All @@ -138,6 +143,8 @@ const MessageToolbar = ({
}))}
onChangeMenuVisibility={onChangeMenuVisibility}
data-qa-type='message-action-menu-options'
context={{ message, room, user, subscription, settings: mapSettings, chat, context }}
isMessageEncrypted={isE2EEMessage(message)}
/>
)}
</FuselageMessageToolbar>
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@
"@rocket.chat/favicon": "workspace:^",
"@rocket.chat/forked-matrix-appservice-bridge": "^4.0.2",
"@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.3",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/fuselage-toastbar": "^0.31.26",
Expand Down
25 changes: 25 additions & 0 deletions apps/meteor/tests/e2e/e2e-encryption.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,31 @@ test.describe.serial('e2e-encryption', () => {
await expect(poHomeChannel.content.mainThreadMessageText.locator('.rcx-icon--name-key')).toBeVisible();
});

test('expect create a private encrypted channel and check disabled message menu actions on 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 is an encrypted message.');

await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.');
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();

await page.locator('[data-qa-type="message"]').last().hover();
await expect(page.locator('role=button[name="Forward message"]')).toBeDisabled();

await poHomeChannel.content.openLastMessageMenu();

await expect(page.locator('role=menuitem[name="Reply in direct message"]')).toHaveClass(/disabled/);
await expect(page.locator('role=menuitem[name="Copy link"]')).toHaveClass(/disabled/);
});

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

Expand Down
2 changes: 1 addition & 1 deletion ee/packages/ui-theming/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "^0.36.0",
"@rocket.chat/ui-contexts": "workspace:~",
Expand Down
2 changes: 1 addition & 1 deletion packages/fuselage-ui-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"@rocket.chat/apps-engine": "alpha",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/icons": "^0.36.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/gazzodown/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-tokens": "^0.33.1",
"@rocket.chat/message-parser": "workspace:^",
"@rocket.chat/styled": "~0.31.25",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@
"Action_required": "Action required",
"Action_Available_After_Custom_Content_Added": "This action will become available after the custom content has been added",
"Action_Available_After_Custom_Content_Added_And_Visible": "This action will become available after the custom content has been added and made visible to everyone",
"Action_not_available_encrypted_content": "{{action}} not available on encrypted content",
"Activate": "Activate",
"Active": "Active",
"Active_users": "Active users",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-avatar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@babel/core": "~7.22.20",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/ui-contexts": "workspace:^",
"@types/babel__core": "~7.20.3",
"@types/react": "~17.0.69",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "^0.36.0",
"@rocket.chat/mock-providers": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/icons": "^0.36.0",
"@storybook/addon-actions": "~6.5.16",
"@storybook/addon-docs": "~6.5.16",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-video-conf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "^0.36.0",
"@rocket.chat/styled": "~0.31.25",
Expand Down
2 changes: 1 addition & 1 deletion packages/uikit-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@codemirror/tooltip": "^0.19.16",
"@lezer/highlight": "^1.1.6",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/fuselage-toastbar": "^0.31.26",
Expand Down
Loading
Loading