From 45dc3d5f725e395aad485d24d10e8f31feb15db0 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Thu, 20 Jun 2024 04:00:40 +0530 Subject: [PATCH] feat: Disable some message menu items for encrypted messages. (#32559) --- .changeset/plenty-buses-kneel.md | 6 +++ .../app/ui-utils/client/lib/MessageAction.ts | 3 +- .../client/lib/messageActionDefault.ts | 11 +++++- .../components/GenericMenu/GenericMenu.tsx | 2 +- .../GenericMenu/GenericMenuItem.tsx | 5 ++- .../message/toolbar/MessageActionMenu.tsx | 38 +++++++++++++++++-- .../message/toolbar/MessageToolbar.tsx | 11 +++++- apps/meteor/package.json | 2 +- apps/meteor/tests/e2e/e2e-encryption.spec.ts | 25 ++++++++++++ ee/packages/ui-theming/package.json | 2 +- packages/fuselage-ui-kit/package.json | 2 +- packages/gazzodown/package.json | 2 +- packages/i18n/src/locales/en.i18n.json | 1 + packages/ui-avatar/package.json | 2 +- packages/ui-client/package.json | 2 +- packages/ui-composer/package.json | 2 +- packages/ui-video-conf/package.json | 2 +- packages/uikit-playground/package.json | 2 +- yarn.lock | 26 ++++++------- 19 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 .changeset/plenty-buses-kneel.md diff --git a/.changeset/plenty-buses-kneel.md b/.changeset/plenty-buses-kneel.md new file mode 100644 index 000000000000..5c21bdb0bb69 --- /dev/null +++ b/.changeset/plenty-buses-kneel.md @@ -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. diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 6a3ddd45ca66..c1f9590b98ee 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -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; @@ -65,6 +65,7 @@ export type MessageActionConfig = { ) => any; condition?: (props: MessageActionConditionProps) => Promise | boolean; type?: MessageActionType; + disabled?: (props: MessageActionConditionProps) => boolean; }; class MessageAction { diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts index 589c387772fb..2f2793f7493b 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts @@ -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'; @@ -63,6 +63,9 @@ Meteor.startup(async () => { }, order: 0, group: 'menu', + disabled({ message }) { + return isE2EEMessage(message); + }, }); MessageAction.addButton({ @@ -87,6 +90,9 @@ Meteor.startup(async () => { }, order: 0, group: 'message', + disabled({ message }) { + return isE2EEMessage(message); + }, }); MessageAction.addButton({ @@ -139,6 +145,9 @@ Meteor.startup(async () => { }, order: 5, group: 'menu', + disabled({ message }) { + return isE2EEMessage(message); + }, }); MessageAction.addButton({ diff --git a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx index 9d8367f7ad98..ddde6122b99d 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx @@ -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); diff --git a/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx index ec987a1ee28d..44feedf86115 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx @@ -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 && } {icon && } {status && {status}} - {content && {content}} + {content && {content}} {addon && {addon}} ); diff --git a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx index 8d266fe35d0e..6c35f7b73dbd 100644 --- a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx @@ -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'; @@ -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' : '', @@ -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 : ''; @@ -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 ( 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 && ( @@ -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)} /> )} diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 9d02e91c6cba..b060c09a53b4 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -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", diff --git a/apps/meteor/tests/e2e/e2e-encryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption.spec.ts index 58adc3e8654b..14d407aaf81d 100644 --- a/apps/meteor/tests/e2e/e2e-encryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption.spec.ts @@ -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(); diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json index 437f1fda92ab..dca631587d3c 100644 --- a/ee/packages/ui-theming/package.json +++ b/ee/packages/ui-theming/package.json @@ -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:~", diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 119a23759280..6f8b6dfefe13 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -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", diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index 0ce09b0aa79b..ace746bfb5d0 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -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", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 863a773d45c6..cfaecfe2c924 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -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", diff --git a/packages/ui-avatar/package.json b/packages/ui-avatar/package.json index a5300bc5648a..1db9cc6c1eb9 100644 --- a/packages/ui-avatar/package.json +++ b/packages/ui-avatar/package.json @@ -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", diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json index 6b6429df099c..a56e6dcc757e 100644 --- a/packages/ui-client/package.json +++ b/packages/ui-client/package.json @@ -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:^", diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json index e256334c3eb4..a36c0c3df151 100644 --- a/packages/ui-composer/package.json +++ b/packages/ui-composer/package.json @@ -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", diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json index 2e92e8158113..4ecf8699aeca 100644 --- a/packages/ui-video-conf/package.json +++ b/packages/ui-video-conf/package.json @@ -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", diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index 133faa8c75ee..732b74c0d3d3 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 6321b0aff77f..ce099ca48a7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8915,7 +8915,7 @@ __metadata: "@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/gazzodown": "workspace:^" @@ -8977,9 +8977,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/fuselage@npm:^0.54.2": - version: 0.54.2 - resolution: "@rocket.chat/fuselage@npm:0.54.2" +"@rocket.chat/fuselage@npm:^0.54.3": + version: 0.54.3 + resolution: "@rocket.chat/fuselage@npm:0.54.3" dependencies: "@rocket.chat/css-in-js": ^0.31.25 "@rocket.chat/css-supports": ^0.31.25 @@ -8997,7 +8997,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 1e49b3324f50525a002dd450a28dc8c6fca559e51c8e93449e167b50ca88767cc3dd11ac3c89ea794597c6c06a4a681aea1043b8f8bb42c49df0362bb5791019 + checksum: bec4d0b92e919103cda927520040f46004266ec5e1b3964c5bec6c6be59f8f051f2940689785f4e78984a9a18230a175b9f5f8e548f2b8f951387d567570735c languageName: node linkType: hard @@ -9008,7 +9008,7 @@ __metadata: "@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 @@ -9368,7 +9368,7 @@ __metadata: "@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 @@ -10264,7 +10264,7 @@ __metadata: resolution: "@rocket.chat/ui-avatar@workspace:packages/ui-avatar" dependencies: "@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 @@ -10290,7 +10290,7 @@ __metadata: "@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:^" @@ -10343,7 +10343,7 @@ __metadata: "@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 @@ -10435,7 +10435,7 @@ __metadata: resolution: "@rocket.chat/ui-theming@workspace:ee/packages/ui-theming" dependencies: "@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:~" @@ -10478,7 +10478,7 @@ __metadata: "@rocket.chat/css-in-js": ~0.31.25 "@rocket.chat/emitter": ~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 @@ -10523,7 +10523,7 @@ __metadata: "@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