From 0cea9a7c5cc6033b4f48fbff59ee7cc1755d5374 Mon Sep 17 00:00:00 2001 From: Przemyslaw Jozwik Date: Thu, 23 Nov 2023 11:43:13 +0100 Subject: [PATCH] feat: Degraded Conversation - plain indication --- src/i18n/en-US.json | 4 + .../E2EIVerificationMessage.styles.ts | 33 ++++++ .../E2EIVerificationMessage.test.tsx | 52 +++++++++ .../E2EIVerificationMessage.tsx | 106 ++++++++++++++++++ .../Message/E2EIVerificationMessage/index.ts | 20 ++++ .../MessagesList/Message/MessageWrapper.tsx | 4 + .../Message/SystemMessage/SystemMessage.tsx | 7 -- .../conversation/ConversationCellState.ts | 3 +- .../conversation/ConversationRepository.ts | 2 +- .../MLS/MLSStateHandler.ts | 10 +- .../shared/changeHandler/index.ts | 18 ++- src/script/conversation/EventBuilder.ts | 32 +++++- src/script/conversation/EventMapper.ts | 28 +++-- src/script/entity/Conversation.ts | 5 +- .../entity/message/E2EIVerificationMessage.ts | 21 ++-- src/script/entity/message/Message.ts | 9 ++ src/script/event/EventTypeHandling.ts | 2 +- src/script/main/app.ts | 15 ++- .../message/E2EIVerificationMessageType.ts | 29 +++++ src/script/message/SuperType.ts | 1 + src/script/message/SystemMessageType.ts | 1 - src/style/common/variables.less | 3 + .../content/conversation/message-list.less | 2 +- 23 files changed, 351 insertions(+), 56 deletions(-) create mode 100644 src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.styles.ts create mode 100644 src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.test.tsx create mode 100644 src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx create mode 100644 src/script/components/MessagesList/Message/E2EIVerificationMessage/index.ts create mode 100644 src/script/message/E2EIVerificationMessageType.ts diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index baacffc394d..405785b7db3 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -1335,6 +1335,10 @@ "tooltipConversationAllDevicesVerified": "All device fingerprints verified (Proteus)", "tooltipConversationAllE2EIVerified": "All devices are verified (end-to-end identity). [link]Learn more[/link]", "tooltipConversationAllE2EIVerifiedShort": "All devices verified (end-to-end identity)", + "tooltipConversationAllE2EINewDeviceAdded": "This conversation is no longer verified, as [bold]{{user}}[/bold] uses at least one device without a valid end-to-end identity certificate.", + "tooltipConversationAllE2EINewUserAdded": "This conversation is no longer verified, as [bold]{{user}}[/bold] uses at least one device without a valid end-to-end identity certificate.", + "tooltipConversationAllE2EICertificateExpired": "This conversation is no longer verified, as [bold]{{user}}[/bold] uses at least one devices with an expired end-to-end identity certificate.", + "tooltipConversationAllE2EICertificateRevoked": "This conversation is no longer verified, as a team admin revoked the end-to-end identity certificate for at least one of [bold]{{user}}[/bold] devices. [link]Learn more[/link]", "tooltipConversationCall": "Call", "tooltipConversationDetailsAddPeople": "Add participants to conversation ({{shortcut}})", "tooltipConversationDetailsRename": "Change conversation name", diff --git a/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.styles.ts b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.styles.ts new file mode 100644 index 00000000000..407bf0f2d51 --- /dev/null +++ b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.styles.ts @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2022 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {CSSObject} from '@emotion/react'; + +export const MessageIcon: CSSObject = { + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + + maxHeight: 'var(--avatar-diameter-s)', + width: 'var(--conversation-message-sender-width)', +}; + +export const IconInfo: CSSObject = { + fill: 'var(--red-500)', +}; diff --git a/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.test.tsx b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.test.tsx new file mode 100644 index 00000000000..25dcccb7822 --- /dev/null +++ b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.test.tsx @@ -0,0 +1,52 @@ +/* + * Wire + * Copyright (C) 2021 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {render} from '@testing-library/react'; +import ko from 'knockout'; + +import {E2EIVerificationMessage as VerificationMessageEntity} from 'src/script/entity/message/E2EIVerificationMessage'; +import {E2EIVerificationMessageType} from 'src/script/message/E2EIVerificationMessageType'; + +import {E2EIVerificationMessage} from './E2EIVerificationMessage'; + +import {withTheme} from '../../../../auth/util/test/TestUtil'; + +const createVerificationMessage = (partialVerificationMessage: Partial) => { + const verificationMessage: Partial = { + ...partialVerificationMessage, + }; + return verificationMessage as VerificationMessageEntity; +}; + +describe('E2EIVerificationMessage', () => { + describe('with verified message', () => { + it('shows verified icon when message is verified', async () => { + const message = createVerificationMessage({ + messageType: ko.observable(E2EIVerificationMessageType.VERIFIED), + }); + + const {getByTestId} = render(withTheme()); + + const elementMessageVerification = getByTestId('element-message-verification'); + expect(elementMessageVerification.getAttribute('data-uie-value')).toEqual(E2EIVerificationMessageType.VERIFIED); + }); + }); + + // TODO: Add more test for another e2ei verification states +}); diff --git a/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx new file mode 100644 index 00000000000..e0f678adf28 --- /dev/null +++ b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx @@ -0,0 +1,106 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {MLSVerified} from '@wireapp/react-ui-kit'; + +import {Icon} from 'Components/Icon'; +import {useKoSubscribableChildren} from 'Util/ComponentUtil'; +import {replaceLink, t} from 'Util/LocalizerUtil'; + +import {MessageIcon, IconInfo} from './E2EIVerificationMessage.styles'; + +import {Config} from '../../../../Config'; +import {E2EIVerificationMessage as E2EIVerificationMessageEntity} from '../../../../entity/message/E2EIVerificationMessage'; +import {E2EIVerificationMessageType} from '../../../../message/E2EIVerificationMessageType'; + +export interface E2EIVerificationMessageProps { + message: E2EIVerificationMessageEntity; +} + +export const E2EIVerificationMessage = ({message}: E2EIVerificationMessageProps) => { + const {messageType} = useKoSubscribableChildren(message, ['messageType']); + + const user = 'Deniz Agha'; + + const isVerified = messageType === E2EIVerificationMessageType.VERIFIED; + const isUnverified = messageType === E2EIVerificationMessageType.UNVERIFIED; + const isNewDevice = messageType === E2EIVerificationMessageType.NEW_DEVICE; + const isNewMember = messageType === E2EIVerificationMessageType.NEW_MEMBER; + const isDegraded = messageType === E2EIVerificationMessageType.DEGRADED; + + const learnMoreReplacement = replaceLink(Config.getConfig().URL.SUPPORT.E2EI_VERIFICATION); + + return ( +
+
+ {isVerified ? ( + + ) : ( + + )} +
+ +
+ {isVerified && ( + + )} + + {isNewDevice && ( + + )} + + {isNewMember && ( + + )} + + {isUnverified && ( + + )} + + {isDegraded && ( + + )} +
+
+ ); +}; diff --git a/src/script/components/MessagesList/Message/E2EIVerificationMessage/index.ts b/src/script/components/MessagesList/Message/E2EIVerificationMessage/index.ts new file mode 100644 index 00000000000..47265d79c87 --- /dev/null +++ b/src/script/components/MessagesList/Message/E2EIVerificationMessage/index.ts @@ -0,0 +1,20 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +export * from './E2EIVerificationMessage'; diff --git a/src/script/components/MessagesList/Message/MessageWrapper.tsx b/src/script/components/MessagesList/Message/MessageWrapper.tsx index 8d0287066b3..6068bbc449c 100644 --- a/src/script/components/MessagesList/Message/MessageWrapper.tsx +++ b/src/script/components/MessagesList/Message/MessageWrapper.tsx @@ -26,6 +26,7 @@ import {container} from 'tsyringe'; import {WebAppEvents} from '@wireapp/webapp-events'; +import {E2EIVerificationMessage} from 'Components/MessagesList/Message/E2EIVerificationMessage'; import {OutgoingQuote} from 'src/script/conversation/MessageRepository'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; import {Text} from 'src/script/entity/message/Text'; @@ -220,6 +221,9 @@ export const MessageWrapper: React.FC; } + if (message.isE2EIVerification()) { + return ; + } if (message.isDelete()) { return ; } diff --git a/src/script/components/MessagesList/Message/SystemMessage/SystemMessage.tsx b/src/script/components/MessagesList/Message/SystemMessage/SystemMessage.tsx index 007e19d6bae..e7afe1fabb3 100644 --- a/src/script/components/MessagesList/Message/SystemMessage/SystemMessage.tsx +++ b/src/script/components/MessagesList/Message/SystemMessage/SystemMessage.tsx @@ -19,10 +19,7 @@ import React from 'react'; -import {MLSVerified} from '@wireapp/react-ui-kit'; - import {Icon} from 'Components/Icon'; -import {E2EIVerificationMessage} from 'src/script/entity/message/E2EIVerificationMessage'; import {MessageTimerUpdateMessage} from 'src/script/entity/message/MessageTimerUpdateMessage'; import {MLSConversationRecoveredMessage} from 'src/script/entity/message/MLSConversationRecoveredMessage'; import {ReceiptModeUpdateMessage} from 'src/script/entity/message/ReceiptModeUpdateMessage'; @@ -57,9 +54,5 @@ export const SystemMessage: React.FC = ({message}) => { return } />; } - if (message instanceof E2EIVerificationMessage) { - return } />; - } - return ; }; diff --git a/src/script/conversation/ConversationCellState.ts b/src/script/conversation/ConversationCellState.ts index db2c3d118a4..628ebac3db1 100644 --- a/src/script/conversation/ConversationCellState.ts +++ b/src/script/conversation/ConversationCellState.ts @@ -30,7 +30,6 @@ import type {MemberMessage} from '../entity/message/MemberMessage'; import type {SystemMessage} from '../entity/message/SystemMessage'; import type {Text} from '../entity/message/Text'; import {ConversationError} from '../error/ConversationError'; -import {ClientEvent} from '../event/Client'; enum ACTIVITY_TYPE { CALL = 'ConversationCellState.ACTIVITY_TYPE.CALL', @@ -335,7 +334,7 @@ const _getStateUnreadMessage = { string = t('notificationSharedLocation'); } else if (messageEntity.hasAssetImage()) { string = t('notificationAssetAdd'); - } else if (messageEntity?.type === ClientEvent.CONVERSATION.E2EI_VERIFICATION) { + } else if (messageEntity.isE2EIVerification()) { string = t('tooltipConversationAllDevicesVerified'); } else if (messageEntity.isVerification()) { string = t('tooltipConversationAllDevicesVerified'); diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index 81c59279a36..df9c18d68e8 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -2879,9 +2879,9 @@ export class ConversationRepository { case ClientEvent.CONVERSATION.MLS_CONVERSATION_RECOVERED: case ClientEvent.CONVERSATION.UNABLE_TO_DECRYPT: case ClientEvent.CONVERSATION.VERIFICATION: + case ClientEvent.CONVERSATION.E2EI_VERIFICATION: case ClientEvent.CONVERSATION.VOICE_CHANNEL_ACTIVATE: case ClientEvent.CONVERSATION.VOICE_CHANNEL_DEACTIVATE: - case ClientEvent.CONVERSATION.E2EI_VERIFICATION: return this.addEventToConversation(conversationEntity, eventJson); } } diff --git a/src/script/conversation/ConversationVerificationStateHandler/MLS/MLSStateHandler.ts b/src/script/conversation/ConversationVerificationStateHandler/MLS/MLSStateHandler.ts index 4538a475041..188d604f3ce 100644 --- a/src/script/conversation/ConversationVerificationStateHandler/MLS/MLSStateHandler.ts +++ b/src/script/conversation/ConversationVerificationStateHandler/MLS/MLSStateHandler.ts @@ -22,20 +22,20 @@ import {container} from 'tsyringe'; import {MLSStatuses} from 'Components/VerificationBadge'; import {getConversationVerificationState, getUsersVerificationState} from 'src/script/E2EIdentity'; -import {VerificationMessageType} from 'src/script/message/VerificationMessageType'; +import {E2EIVerificationMessageType} from 'src/script/message/E2EIVerificationMessageType'; import {Core} from 'src/script/service/CoreSingleton'; import {Logger, getLogger} from 'Util/Logger'; import {MLSConversation} from '../../ConversationSelectors'; import {ConversationState} from '../../ConversationState'; import {ConversationVerificationState} from '../../ConversationVerificationState'; -import {getConversationByGroupId, OnConversationVerificationStateChange} from '../shared'; +import {getConversationByGroupId, OnConversationE2EIVerificationStateChange} from '../shared'; class MLSConversationVerificationStateHandler { private readonly logger: Logger; public constructor( - private readonly onConversationVerificationStateChange: OnConversationVerificationStateChange, + private readonly onConversationVerificationStateChange: OnConversationE2EIVerificationStateChange, private readonly conversationState: ConversationState, private readonly core: Core, ) { @@ -64,7 +64,7 @@ class MLSConversationVerificationStateHandler { this.onConversationVerificationStateChange({ conversationEntity: conversation, conversationVerificationState: state, - verificationMessageType: VerificationMessageType.UNVERIFIED, + verificationMessageType: E2EIVerificationMessageType.UNVERIFIED, userIds: degradedUsers, }); } @@ -107,7 +107,7 @@ class MLSConversationVerificationStateHandler { } export const registerMLSConversationVerificationStateHandler = ( - onConversationVerificationStateChange: OnConversationVerificationStateChange = () => {}, + onConversationVerificationStateChange: OnConversationE2EIVerificationStateChange = () => {}, conversationState: ConversationState = container.resolve(ConversationState), core: Core = container.resolve(Core), ): void => { diff --git a/src/script/conversation/ConversationVerificationStateHandler/shared/changeHandler/index.ts b/src/script/conversation/ConversationVerificationStateHandler/shared/changeHandler/index.ts index 9c7d26ee7f6..9c29750fd4f 100644 --- a/src/script/conversation/ConversationVerificationStateHandler/shared/changeHandler/index.ts +++ b/src/script/conversation/ConversationVerificationStateHandler/shared/changeHandler/index.ts @@ -23,10 +23,24 @@ import {ConversationVerificationState} from 'src/script/conversation/Conversatio import {Conversation} from 'src/script/entity/Conversation'; import {VerificationMessageType} from 'src/script/message/VerificationMessageType'; -interface OnConversationVerificationStateChangeParams { +import {E2EIVerificationMessageType} from '../../../../message/E2EIVerificationMessageType'; + +interface CommonOnConversationVerificationStateChangeParams { conversationEntity: Conversation; conversationVerificationState: ConversationVerificationState; - verificationMessageType?: VerificationMessageType; userIds?: QualifiedId[]; } + +interface OnConversationVerificationStateChangeParams extends CommonOnConversationVerificationStateChangeParams { + verificationMessageType?: VerificationMessageType; +} + +interface OnConversationE2EIVerificationStateChangeParams extends CommonOnConversationVerificationStateChangeParams { + verificationMessageType?: E2EIVerificationMessageType; +} + export type OnConversationVerificationStateChange = (params: OnConversationVerificationStateChangeParams) => void; + +export type OnConversationE2EIVerificationStateChange = ( + params: OnConversationE2EIVerificationStateChangeParams, +) => void; diff --git a/src/script/conversation/EventBuilder.ts b/src/script/conversation/EventBuilder.ts index 4cff0074089..6c8483797b7 100644 --- a/src/script/conversation/EventBuilder.ts +++ b/src/script/conversation/EventBuilder.ts @@ -19,9 +19,9 @@ import {MemberLeaveReason} from '@wireapp/api-client/lib/conversation/data'; import { - ConversationOtrMessageAddEvent, - ConversationMLSMessageAddEvent, CONVERSATION_EVENT, + ConversationMLSMessageAddEvent, + ConversationOtrMessageAddEvent, } from '@wireapp/api-client/lib/event'; import type {QualifiedId} from '@wireapp/api-client/lib/user'; import {AddUsersFailureReasons} from '@wireapp/core/lib/conversation'; @@ -29,7 +29,7 @@ import {ReactionType} from '@wireapp/core/lib/conversation/ReactionType'; import {DecryptionError} from '@wireapp/core/lib/errors/DecryptionError'; import type {REASON as AVS_REASON} from '@wireapp/avs'; -import type {LegalHoldStatus, Asset} from '@wireapp/protocol-messaging'; +import type {Asset, LegalHoldStatus} from '@wireapp/protocol-messaging'; import {createUuid} from 'Util/uuid'; @@ -39,6 +39,7 @@ import type {Conversation} from '../entity/Conversation'; import type {Message} from '../entity/message/Message'; import type {User} from '../entity/User'; import {CALL, ClientEvent, CONVERSATION} from '../event/Client'; +import {E2EIVerificationMessageType} from '../message/E2EIVerificationMessageType'; import {StatusType} from '../message/StatusType'; import {VerificationMessageType} from '../message/VerificationMessageType'; import {ReactionMap, ReadReceipt, UserReactionMap} from '../storage'; @@ -238,8 +239,12 @@ export interface ErrorEvent id: string; } -// E2EI Verified Events -export type AllE2EIVerifiedEvent = ConversationEvent; +// E2EI Verification Events +export type AllE2EIVerifiedEventData = {type: E2EIVerificationMessageType}; +export type AllE2EIVerifiedEvent = ConversationEvent; + +export type E2EIDegradedMessageEventData = {type: E2EIVerificationMessageType}; +export type E2EIDegradedMessageEvent = ConversationEvent; export type ClientConversationEvent = | AllVerifiedEvent @@ -316,7 +321,22 @@ export const EventBuilder = { buildAllE2EIVerified(conversationEntity: Conversation): AllE2EIVerifiedEvent { return { ...buildQualifiedId(conversationEntity), - data: undefined, + data: { + type: E2EIVerificationMessageType.VERIFIED, + }, + from: '', + id: createUuid(), + time: conversationEntity.getNextIsoDate(), + type: ClientEvent.CONVERSATION.E2EI_VERIFICATION, + }; + }, + + buildE2EIDegraded(conversationEntity: Conversation, type: E2EIVerificationMessageType): E2EIDegradedMessageEvent { + return { + ...buildQualifiedId(conversationEntity), + data: { + type, + }, from: '', id: createUuid(), time: conversationEntity.getNextIsoDate(), diff --git a/src/script/conversation/EventMapper.ts b/src/script/conversation/EventMapper.ts index 232d11d17fe..0a20f14e0ae 100644 --- a/src/script/conversation/EventMapper.ts +++ b/src/script/conversation/EventMapper.ts @@ -332,11 +332,6 @@ export class EventMapper { break; } - case ClientEvent.CONVERSATION.E2EI_VERIFICATION: { - messageEntity = this._mapEventE2EIVerificationMessage(); - break; - } - case ClientEvent.CONVERSATION.ONE2ONE_CREATION: { messageEntity = this._mapEvent1to1Creation(event); break; @@ -352,6 +347,11 @@ export class EventMapper { break; } + case ClientEvent.CONVERSATION.E2EI_VERIFICATION: { + messageEntity = this._mapEventE2EIVerificationMessage(event); + break; + } + case ClientEvent.CONVERSATION.VOICE_CHANNEL_ACTIVATE: { messageEntity = this._mapEventVoiceChannelActivate(); break; @@ -649,13 +649,6 @@ export class EventMapper { return new MLSConversationRecoveredMessage(); } - /** - * Maps JSON data of E2E Identity verification message event to message entity. - */ - private _mapEventE2EIVerificationMessage(): MissedMessage { - return new E2EIVerificationMessage(); - } - /** * Maps JSON data of `conversation.knock` message into message entity. */ @@ -733,6 +726,17 @@ export class EventMapper { return messageEntity; } + /** + * Maps JSON data of E2E Identity verification message event to message entity. + */ + private _mapEventE2EIVerificationMessage({data: eventData}: LegacyEventRecord): MissedMessage { + const messageEntity = new E2EIVerificationMessage(); + // Database can contain non-camelCased naming. For backwards compatibility reasons we handle both. + messageEntity.messageType(eventData.type); + + return messageEntity; + } + /** * Maps JSON data of conversation.voice-channel-activate message into message entity. * @returns Call message entity diff --git a/src/script/entity/Conversation.ts b/src/script/entity/Conversation.ts index 0a0e2e9072c..39ca7c1f43e 100644 --- a/src/script/entity/Conversation.ts +++ b/src/script/entity/Conversation.ts @@ -58,7 +58,6 @@ import {ConversationStatus} from '../conversation/ConversationStatus'; import {ConversationVerificationState} from '../conversation/ConversationVerificationState'; import {NOTIFICATION_STATE} from '../conversation/NotificationSetting'; import {ConversationError} from '../error/ConversationError'; -import {ClientEvent} from '../event/Client'; import {isContentMessage, isDeleteMessage} from '../guards/Message'; import {StatusType} from '../message/StatusType'; import {ConversationRecord} from '../storage/record/ConversationRecord'; @@ -456,9 +455,7 @@ export class Conversation { const isSelfQuoted = isMessage && this.selfUser() && (messageEntity as ContentMessage).isUserQuoted(this.selfUser().id); - const isMLSProtocol = this.protocol === ConversationProtocol.MLS; - const isE2EIVerification = - isMLSProtocol && messageEntity?.type === ClientEvent.CONVERSATION.E2EI_VERIFICATION; + const isE2EIVerification = messageEntity.isE2EIVerification(); if (isMissedCall || isPing || isMessage || isE2EIVerification) { unreadState.allMessages.push(messageEntity as ContentMessage); diff --git a/src/script/entity/message/E2EIVerificationMessage.ts b/src/script/entity/message/E2EIVerificationMessage.ts index 7aefd6aac4e..9b8eb008d6f 100644 --- a/src/script/entity/message/E2EIVerificationMessage.ts +++ b/src/script/entity/message/E2EIVerificationMessage.ts @@ -17,21 +17,20 @@ * */ -import {replaceLink, t} from 'Util/LocalizerUtil'; +import ko from 'knockout'; -import {SystemMessage} from './SystemMessage'; +import {Message} from './Message'; -import {Config} from '../../Config'; -import {SystemMessageType} from '../../message/SystemMessageType'; +import {E2EIVerificationMessageType} from '../../message/E2EIVerificationMessageType'; +import {SuperType} from '../../message/SuperType'; + +export class E2EIVerificationMessage extends Message { + public messageType: ko.Observable; -export class E2EIVerificationMessage extends SystemMessage { constructor() { super(); - this.system_message_type = SystemMessageType.E2EI_VERIFIED; - this.caption = t( - 'tooltipConversationAllE2EIVerified', - {}, - replaceLink(Config.getConfig().URL.SUPPORT.E2EI_VERIFICATION), - ); + + this.super_type = SuperType.E2EI_VERIFICATION; + this.messageType = ko.observable(); } } diff --git a/src/script/entity/message/Message.ts b/src/script/entity/message/Message.ts index fdb742500df..9a06071c852 100644 --- a/src/script/entity/message/Message.ts +++ b/src/script/entity/message/Message.ts @@ -31,6 +31,7 @@ import type {CompositeMessage} from './CompositeMessage'; import type {ContentMessage} from './ContentMessage'; import type {DecryptErrorMessage} from './DecryptErrorMessage'; import type {DeleteMessage} from './DeleteMessage'; +import {E2EIVerificationMessage} from './E2EIVerificationMessage'; import type {FailedToAddUsersMessage} from './FailedToAddUsersMessage'; import type {FederationStopMessage} from './FederationStopMessage'; import type {FileAsset} from './FileAsset'; @@ -320,6 +321,14 @@ export class Message { return this.super_type === SuperType.VERIFICATION; } + /** + * Check if message is a E2E Identity Verification message. + * @returns Is message of type E2E Identity Verification + */ + isE2EIVerification(): this is E2EIVerificationMessage { + return this.super_type === SuperType.E2EI_VERIFICATION; + } + isFederationStop(): this is FederationStopMessage { return this.super_type === SuperType.FEDERATION_STOP; } diff --git a/src/script/event/EventTypeHandling.ts b/src/script/event/EventTypeHandling.ts index 915aad61dc8..21c77632390 100644 --- a/src/script/event/EventTypeHandling.ts +++ b/src/script/event/EventTypeHandling.ts @@ -60,8 +60,8 @@ export const EventTypeHandling = { ClientEvent.CONVERSATION.TEAM_MEMBER_LEAVE, ClientEvent.CONVERSATION.UNABLE_TO_DECRYPT, ClientEvent.CONVERSATION.VERIFICATION, + ClientEvent.CONVERSATION.E2EI_VERIFICATION, ClientEvent.CONVERSATION.VOICE_CHANNEL_ACTIVATE, ClientEvent.CONVERSATION.VOICE_CHANNEL_DEACTIVATE, - ClientEvent.CONVERSATION.E2EI_VERIFICATION, ], }; diff --git a/src/script/main/app.ts b/src/script/main/app.ts index b5947b4018b..d3eaf8897d5 100644 --- a/src/script/main/app.ts +++ b/src/script/main/app.ts @@ -60,7 +60,7 @@ import {ConversationRepository} from '../conversation/ConversationRepository'; import {ConversationService} from '../conversation/ConversationService'; import {ConversationVerificationState} from '../conversation/ConversationVerificationState'; import {registerMLSConversationVerificationStateHandler} from '../conversation/ConversationVerificationStateHandler'; -import {OnConversationVerificationStateChange} from '../conversation/ConversationVerificationStateHandler/shared'; +import {OnConversationE2EIVerificationStateChange} from '../conversation/ConversationVerificationStateHandler/shared'; import {EventBuilder} from '../conversation/EventBuilder'; import {MessageRepository} from '../conversation/MessageRepository'; import {CryptographyRepository} from '../cryptography/CryptographyRepository'; @@ -376,7 +376,7 @@ export class App { } if (supportsMLS()) { - registerMLSConversationVerificationStateHandler(this.updateConversationVerificationState); + registerMLSConversationVerificationStateHandler(this.updateConversationE2EIVerificationState); } this.core.on(CoreEvents.NEW_SESSION, ({userId, clientId}) => { @@ -807,15 +807,24 @@ export class App { doRedirect(signOutReason); } - private updateConversationVerificationState: OnConversationVerificationStateChange = async ({ + private updateConversationE2EIVerificationState: OnConversationE2EIVerificationStateChange = async ({ conversationEntity, conversationVerificationState, + verificationMessageType, }) => { switch (conversationVerificationState) { case ConversationVerificationState.VERIFIED: const allVerifiedEvent = EventBuilder.buildAllE2EIVerified(conversationEntity); await this.repository.event.injectEvent(allVerifiedEvent); break; + case ConversationVerificationState.DEGRADED: + if (verificationMessageType) { + const degradedEvent = EventBuilder.buildE2EIDegraded(conversationEntity, verificationMessageType); + await this.repository.event.injectEvent(degradedEvent); + } else { + this.logger.error('updateConversationE2EIVerificationState: Missing verificationMessageType while degrading'); + } + break; default: break; } diff --git a/src/script/message/E2EIVerificationMessageType.ts b/src/script/message/E2EIVerificationMessageType.ts new file mode 100644 index 00000000000..622c3c273e0 --- /dev/null +++ b/src/script/message/E2EIVerificationMessageType.ts @@ -0,0 +1,29 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +export enum E2EIVerificationMessageType { + // DEGRADED States + DEGRADED = 'degraded', + NEW_DEVICE = 'new-device', + NEW_MEMBER = 'new-member', + EXPIRED = 'expired', + REVOKED = 'revoked', + VERIFIED = 'verified', + UNVERIFIED = 'unverified', +} diff --git a/src/script/message/SuperType.ts b/src/script/message/SuperType.ts index 0a7f6504372..a7169c03a13 100644 --- a/src/script/message/SuperType.ts +++ b/src/script/message/SuperType.ts @@ -37,4 +37,5 @@ export enum SuperType { SYSTEM = 'system', UNABLE_TO_DECRYPT = 'unable-to-decrypt', VERIFICATION = 'verification', + E2EI_VERIFICATION = 'e2e-verification', } diff --git a/src/script/message/SystemMessageType.ts b/src/script/message/SystemMessageType.ts index ccea1db3d59..8a638ea4148 100644 --- a/src/script/message/SystemMessageType.ts +++ b/src/script/message/SystemMessageType.ts @@ -34,5 +34,4 @@ export enum SystemMessageType { MEMBER_LEAVE = 'leave', NORMAL = 'normal', MLS_CONVERSATION_RECOVERED = 'mls-conversation-recovered', - E2EI_VERIFIED = 'e2ei-verified', } diff --git a/src/style/common/variables.less b/src/style/common/variables.less index 2295a932bb9..a0c86247eb0 100644 --- a/src/style/common/variables.less +++ b/src/style/common/variables.less @@ -239,6 +239,8 @@ body { @conversation-message-sender-width: 56px; body { + --conversation-message-sender-width: @conversation-message-sender-width; + --conversation-message-timestamp-width: 140px; @media (max-width: @screen-md-min) { --conversation-message-timestamp-width: 60px; @@ -287,6 +289,7 @@ body { // Avatar // --------------------------------------------------------------------------- body { + --avatar-diameter-s: 28px; --avatar-diameter-m: 40px; --font-weight-semibold: 600; --font-weight-medium: 500; diff --git a/src/style/content/conversation/message-list.less b/src/style/content/conversation/message-list.less index e64075a97b2..f41bdb66fa9 100644 --- a/src/style/content/conversation/message-list.less +++ b/src/style/content/conversation/message-list.less @@ -156,7 +156,7 @@ &--svg { line-height: 0; - svg:not(.filled) path { + svg path { fill: var(--foreground); } }