mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
Replace event verification logic with new code in js-sdk (#11528)
* Use new crypto-api for cross user verification * update verification flow with new APIs * Replace some calls to `checkUserTrust` A start on https://github.com/vector-im/crypto-internal/issues/147 * Enable cypress tests * update tests * Delegate decisions on event shields to the js-sdk * rerender after editing events This is required because a transition from "valid event" to "unencrypted event" no longer triggers a state change, so the component does not render itself. Previously, this would be a transition from `verified: E2EState.Normal` to `verified: null`. * Update tests * prettier * Test coverage --------- Co-authored-by: Florian Duros <florianduros@element.io>
This commit is contained in:
parent
579b0dd10a
commit
cf2340bcad
@ -409,7 +409,7 @@ describe("Cryptography", function () {
|
||||
.should("contain", "test encrypted from unverified")
|
||||
.find(".mx_EventTile_e2eIcon", { timeout: 100000 })
|
||||
.should("have.class", "mx_EventTile_e2eIcon_warning")
|
||||
.should("have.attr", "aria-label", "Encrypted by an unverified session");
|
||||
.should("have.attr", "aria-label", "Encrypted by an unverified user.");
|
||||
|
||||
/* Should show a grey padlock for a message from an unknown device */
|
||||
|
||||
@ -422,7 +422,7 @@ describe("Cryptography", function () {
|
||||
.should("contain", "test encrypted from unverified")
|
||||
.find(".mx_EventTile_e2eIcon")
|
||||
.should("have.class", "mx_EventTile_e2eIcon_normal")
|
||||
.should("have.attr", "aria-label", "Encrypted by a deleted session");
|
||||
.should("have.attr", "aria-label", "Encrypted by an unknown or deleted device.");
|
||||
});
|
||||
|
||||
it("Should show a grey padlock for a key restored from backup", () => {
|
||||
|
@ -846,17 +846,17 @@ $left-gutter: 64px;
|
||||
}
|
||||
|
||||
&.mx_EventTile_e2eIcon_warning::after {
|
||||
mask-image: url("$(res)/img/e2e/warning.svg");
|
||||
background-color: $e2e-warning-color;
|
||||
mask-image: url("$(res)/img/e2e/warning.svg"); // (!) in a shield
|
||||
background-color: $e2e-warning-color; // red
|
||||
}
|
||||
|
||||
&.mx_EventTile_e2eIcon_normal::after {
|
||||
mask-image: url("$(res)/img/e2e/normal.svg");
|
||||
background-color: $header-panel-text-primary-color;
|
||||
mask-image: url("$(res)/img/e2e/normal.svg"); // regular shield
|
||||
background-color: $header-panel-text-primary-color; // grey
|
||||
}
|
||||
|
||||
&.mx_EventTile_e2eIcon_decryption_failure::after {
|
||||
mask-image: url("$(res)/img/e2e/decryption-failure.svg");
|
||||
mask-image: url("$(res)/img/e2e/decryption-failure.svg"); // key in a circle
|
||||
background-color: $secondary-content;
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,17 @@ limitations under the License.
|
||||
import React, { createRef, forwardRef, MouseEvent, ReactNode, useRef } from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
EventType,
|
||||
MsgType,
|
||||
RelationType,
|
||||
EventStatus,
|
||||
EventType,
|
||||
MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
RoomMember,
|
||||
MsgType,
|
||||
NotificationCountType,
|
||||
Relations,
|
||||
RelationType,
|
||||
Room,
|
||||
RoomEvent,
|
||||
Relations,
|
||||
RoomMember,
|
||||
Thread,
|
||||
ThreadEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
@ -36,6 +36,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { CallErrorCode } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
import { EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import ReplyChain from "../elements/ReplyChain";
|
||||
import { _t } from "../../../languageHandler";
|
||||
@ -44,7 +45,6 @@ import { Layout } from "../../../settings/enums/Layout";
|
||||
import { formatTime } from "../../../DateUtils";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { DecryptionFailureBody } from "../messages/DecryptionFailureBody";
|
||||
import { E2EState } from "./E2EIcon";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import MessageContextMenu from "../context_menus/MessageContextMenu";
|
||||
import { aboveRightOf } from "../../structures/ContextMenu";
|
||||
@ -236,8 +236,19 @@ export interface EventTileProps {
|
||||
interface IState {
|
||||
// Whether the action bar is focused.
|
||||
actionBarFocused: boolean;
|
||||
// Whether the event's sender has been verified.
|
||||
verified: string | null;
|
||||
|
||||
/**
|
||||
* E2EE shield we should show for decryption problems.
|
||||
*
|
||||
* Note this will be `EventShieldColour.NONE` for all unencrypted events, **including those in encrypted rooms**.
|
||||
*/
|
||||
shieldColour: EventShieldColour;
|
||||
|
||||
/**
|
||||
* Reason code for the E2EE shield. `null` if `shieldColour` is `EventShieldColour.NONE`
|
||||
*/
|
||||
shieldReason: EventShieldReason | null;
|
||||
|
||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||
reactions?: Relations | null | undefined;
|
||||
|
||||
@ -299,9 +310,10 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
this.state = {
|
||||
// Whether the action bar is focused.
|
||||
actionBarFocused: false,
|
||||
// Whether the event's sender has been verified. `null` if no attempt has yet been made to verify
|
||||
// (including if the event is not encrypted).
|
||||
verified: null,
|
||||
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
|
||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||
reactions: this.getReactions(),
|
||||
|
||||
@ -437,8 +449,9 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<EventTileProps>, prevState: Readonly<IState>): void {
|
||||
// If the verification state changed, the height might have changed
|
||||
if (prevState.verified !== this.state.verified && this.props.onHeightChanged) {
|
||||
// If the shield state changed, the height might have changed.
|
||||
// XXX: does the shield *actually* cause a change in height? Not sure.
|
||||
if (prevState.shieldColour !== this.state.shieldColour && this.props.onHeightChanged) {
|
||||
this.props.onHeightChanged();
|
||||
}
|
||||
// If we're not listening for receipts and expect to be, register a listener.
|
||||
@ -582,59 +595,20 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
const mxEvent = this.props.mxEvent.replacingEvent() ?? this.props.mxEvent;
|
||||
|
||||
if (!mxEvent.isEncrypted() || mxEvent.isRedacted()) {
|
||||
this.setState({ verified: null });
|
||||
this.setState({ shieldColour: EventShieldColour.NONE, shieldReason: null });
|
||||
return;
|
||||
}
|
||||
|
||||
const encryptionInfo = MatrixClientPeg.safeGet().getEventEncryptionInfo(mxEvent);
|
||||
const senderId = mxEvent.getSender();
|
||||
if (!senderId) {
|
||||
// something definitely wrong is going on here
|
||||
this.setState({ verified: E2EState.Warning });
|
||||
return;
|
||||
}
|
||||
|
||||
const userTrust = MatrixClientPeg.safeGet().checkUserTrust(senderId);
|
||||
|
||||
if (encryptionInfo.mismatchedSender) {
|
||||
// something definitely wrong is going on here
|
||||
this.setState({ verified: E2EState.Warning });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userTrust.isCrossSigningVerified()) {
|
||||
// If the message is unauthenticated, then display a grey
|
||||
// shield, otherwise if the user isn't cross-signed then
|
||||
// nothing's needed
|
||||
this.setState({ verified: encryptionInfo.authenticated ? E2EState.Normal : E2EState.Unauthenticated });
|
||||
return;
|
||||
}
|
||||
|
||||
const eventSenderTrust =
|
||||
senderId &&
|
||||
encryptionInfo.sender &&
|
||||
(await MatrixClientPeg.safeGet()
|
||||
.getCrypto()
|
||||
?.getDeviceVerificationStatus(senderId, encryptionInfo.sender.deviceId));
|
||||
|
||||
const encryptionInfo =
|
||||
(await MatrixClientPeg.safeGet().getCrypto()?.getEncryptionInfoForEvent(mxEvent)) ?? null;
|
||||
if (this.unmounted) return;
|
||||
|
||||
if (!eventSenderTrust) {
|
||||
this.setState({ verified: E2EState.Unknown });
|
||||
if (encryptionInfo === null) {
|
||||
// likely a decryption error
|
||||
this.setState({ shieldColour: EventShieldColour.NONE, shieldReason: null });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventSenderTrust.isVerified()) {
|
||||
this.setState({ verified: E2EState.Warning });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!encryptionInfo.authenticated) {
|
||||
this.setState({ verified: E2EState.Unauthenticated });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ verified: E2EState.Verified });
|
||||
this.setState({ shieldColour: encryptionInfo.shieldColour, shieldReason: encryptionInfo.shieldReason });
|
||||
}
|
||||
|
||||
private propsEqual(objA: EventTileProps, objB: EventTileProps): boolean {
|
||||
@ -751,18 +725,42 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
return <E2ePadlockDecryptionFailure />;
|
||||
}
|
||||
|
||||
// event is encrypted and not redacted, display padlock corresponding to whether or not it is verified
|
||||
if (ev.isEncrypted() && !ev.isRedacted()) {
|
||||
if (this.state.verified === E2EState.Normal) {
|
||||
return null; // no icon if we've not even cross-signed the user
|
||||
} else if (this.state.verified === E2EState.Verified) {
|
||||
return null; // no icon for verified
|
||||
} else if (this.state.verified === E2EState.Unauthenticated) {
|
||||
return <E2ePadlockUnauthenticated />;
|
||||
} else if (this.state.verified === E2EState.Unknown) {
|
||||
return <E2ePadlockUnknown />;
|
||||
if (this.state.shieldColour !== EventShieldColour.NONE) {
|
||||
let shieldReasonMessage: string;
|
||||
switch (this.state.shieldReason) {
|
||||
case null:
|
||||
case EventShieldReason.UNKNOWN:
|
||||
shieldReasonMessage = _t("Unknown error");
|
||||
break;
|
||||
|
||||
case EventShieldReason.UNVERIFIED_IDENTITY:
|
||||
shieldReasonMessage = _t("Encrypted by an unverified user.");
|
||||
break;
|
||||
|
||||
case EventShieldReason.UNSIGNED_DEVICE:
|
||||
shieldReasonMessage = _t("Encrypted by a device not verified by its owner.");
|
||||
break;
|
||||
|
||||
case EventShieldReason.UNKNOWN_DEVICE:
|
||||
shieldReasonMessage = _t("Encrypted by an unknown or deleted device.");
|
||||
break;
|
||||
|
||||
case EventShieldReason.AUTHENTICITY_NOT_GUARANTEED:
|
||||
shieldReasonMessage = _t(
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.",
|
||||
);
|
||||
break;
|
||||
|
||||
case EventShieldReason.MISMATCHED_SENDER_KEY:
|
||||
shieldReasonMessage = _t("Encrypted by an unverified session");
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.state.shieldColour === EventShieldColour.GREY) {
|
||||
return <E2ePadlock icon={E2ePadlockIcon.Normal} title={shieldReasonMessage} />;
|
||||
} else {
|
||||
return <E2ePadlockUnverified />;
|
||||
// red, by elimination
|
||||
return <E2ePadlock icon={E2ePadlockIcon.Warning} title={shieldReasonMessage} />;
|
||||
}
|
||||
}
|
||||
|
||||
@ -781,9 +779,11 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
if (ev.isRedacted()) {
|
||||
return null; // we expect this to be unencrypted
|
||||
}
|
||||
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
||||
if (!ev.isEncrypted()) {
|
||||
// if the event is not encrypted, but it's an e2e room, show a warning
|
||||
return <E2ePadlockUnencrypted />;
|
||||
}
|
||||
}
|
||||
|
||||
// no padlock needed
|
||||
return null;
|
||||
@ -1460,28 +1460,10 @@ const SafeEventTile = forwardRef<UnwrappedEventTile, EventTileProps>((props, ref
|
||||
});
|
||||
export default SafeEventTile;
|
||||
|
||||
function E2ePadlockUnverified(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {
|
||||
return <E2ePadlock title={_t("Encrypted by an unverified session")} icon={E2ePadlockIcon.Warning} {...props} />;
|
||||
}
|
||||
|
||||
function E2ePadlockUnencrypted(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {
|
||||
return <E2ePadlock title={_t("Unencrypted")} icon={E2ePadlockIcon.Warning} {...props} />;
|
||||
}
|
||||
|
||||
function E2ePadlockUnknown(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {
|
||||
return <E2ePadlock title={_t("Encrypted by a deleted session")} icon={E2ePadlockIcon.Normal} {...props} />;
|
||||
}
|
||||
|
||||
function E2ePadlockUnauthenticated(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {
|
||||
return (
|
||||
<E2ePadlock
|
||||
title={_t("The authenticity of this encrypted message can't be guaranteed on this device.")}
|
||||
icon={E2ePadlockIcon.Normal}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockDecryptionFailure(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {
|
||||
return (
|
||||
<E2ePadlock
|
||||
@ -1493,8 +1475,13 @@ function E2ePadlockDecryptionFailure(props: Omit<IE2ePadlockProps, "title" | "ic
|
||||
}
|
||||
|
||||
enum E2ePadlockIcon {
|
||||
/** grey shield */
|
||||
Normal = "normal",
|
||||
|
||||
/** red shield with (!) */
|
||||
Warning = "warning",
|
||||
|
||||
/** key in grey circle */
|
||||
DecryptionFailure = "decryption_failure",
|
||||
}
|
||||
|
||||
|
@ -2046,12 +2046,14 @@
|
||||
"Everyone in this room is verified": "Everyone in this room is verified",
|
||||
"Edit message": "Edit message",
|
||||
"From a thread": "From a thread",
|
||||
"Encrypted by an unverified user.": "Encrypted by an unverified user.",
|
||||
"Encrypted by a device not verified by its owner.": "Encrypted by a device not verified by its owner.",
|
||||
"Encrypted by an unknown or deleted device.": "Encrypted by an unknown or deleted device.",
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.": "The authenticity of this encrypted message can't be guaranteed on this device.",
|
||||
"Encrypted by an unverified session": "Encrypted by an unverified session",
|
||||
"This event could not be displayed": "This event could not be displayed",
|
||||
" in <strong>%(room)s</strong>": " in <strong>%(room)s</strong>",
|
||||
"Encrypted by an unverified session": "Encrypted by an unverified session",
|
||||
"Unencrypted": "Unencrypted",
|
||||
"Encrypted by a deleted session": "Encrypted by a deleted session",
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.": "The authenticity of this encrypted message can't be guaranteed on this device.",
|
||||
"This message could not be decrypted": "This message could not be decrypted",
|
||||
"Sending your message…": "Sending your message…",
|
||||
"Encrypting your message…": "Encrypting your message…",
|
||||
|
@ -15,31 +15,40 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { render, waitFor, screen, act, fireEvent } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
import {
|
||||
EventType,
|
||||
CryptoApi,
|
||||
TweakName,
|
||||
NotificationCountType,
|
||||
Room,
|
||||
MatrixEvent,
|
||||
EventType,
|
||||
IEventDecryptionResult,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
NotificationCountType,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
TweakName,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
import { IEncryptedEventInfo } from "matrix-js-sdk/src/crypto/api";
|
||||
import { EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api";
|
||||
import { CryptoBackend } from "matrix-js-sdk/src/common-crypto/CryptoBackend";
|
||||
|
||||
import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { flushPromises, getRoomContext, mkEncryptedEvent, mkEvent, mkMessage, stubClient } from "../../../test-utils";
|
||||
import {
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
getRoomContext,
|
||||
mkEncryptedEvent,
|
||||
mkEvent,
|
||||
mkMessage,
|
||||
stubClient,
|
||||
} from "../../../test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
|
||||
describe("EventTile", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
@ -49,12 +58,22 @@ describe("EventTile", () => {
|
||||
|
||||
// let changeEvent: (event: MatrixEvent) => void;
|
||||
|
||||
function TestEventTile(props: Partial<EventTileProps>) {
|
||||
// const [event] = useState(mxEvent);
|
||||
// Give a way for a test to update the event prop.
|
||||
// changeEvent = setEvent;
|
||||
|
||||
return <EventTile mxEvent={mxEvent} {...props} />;
|
||||
/** wrap the EventTile up in context providers, and with basic properties, as it would be by MessagePanel normally. */
|
||||
function WrappedEventTile(props: {
|
||||
roomContext: IRoomState;
|
||||
eventTilePropertyOverrides?: Partial<EventTileProps>;
|
||||
}) {
|
||||
return (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={props.roomContext}>
|
||||
<EventTile
|
||||
mxEvent={mxEvent}
|
||||
replacingEventId={mxEvent.replacingEventId()}
|
||||
{...(props.eventTilePropertyOverrides ?? {})}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function getComponent(
|
||||
@ -64,14 +83,7 @@ describe("EventTile", () => {
|
||||
const context = getRoomContext(room, {
|
||||
timelineRenderingType: renderingType,
|
||||
});
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={context}>
|
||||
<TestEventTile {...overrides} />
|
||||
</RoomContext.Provider>
|
||||
,
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
return render(<WrappedEventTile roomContext={context} eventTilePropertyOverrides={overrides} />);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -196,34 +208,15 @@ describe("EventTile", () => {
|
||||
});
|
||||
});
|
||||
describe("Event verification", () => {
|
||||
// data for our stubbed getEventEncryptionInfo: a map from event id to result
|
||||
const eventToEncryptionInfoMap = new Map<string, IEncryptedEventInfo>();
|
||||
|
||||
const TRUSTED_DEVICE = DeviceInfo.fromStorage({}, "TRUSTED_DEVICE");
|
||||
const UNTRUSTED_DEVICE = DeviceInfo.fromStorage({}, "UNTRUSTED_DEVICE");
|
||||
// data for our stubbed getEncryptionInfoForEvent: a map from event id to result
|
||||
const eventToEncryptionInfoMap = new Map<string, EventEncryptionInfo>();
|
||||
|
||||
beforeEach(() => {
|
||||
eventToEncryptionInfoMap.clear();
|
||||
|
||||
// a mocked version of getEventEncryptionInfo which will pick its result from `eventToEncryptionInfoMap`
|
||||
client.getEventEncryptionInfo = (event) => eventToEncryptionInfoMap.get(event.getId()!)!;
|
||||
|
||||
// a mocked version of checkUserTrust which always says the user is trusted (we do our testing via
|
||||
// unverified devices).
|
||||
const trustedUserTrustLevel = new UserTrustLevel(true, true, true);
|
||||
client.checkUserTrust = (_userId) => trustedUserTrustLevel;
|
||||
|
||||
// a version of checkDeviceTrust which says that TRUSTED_DEVICE is trusted, and others are not.
|
||||
const trustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, true, false);
|
||||
const untrustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, false, false);
|
||||
const mockCrypto = {
|
||||
getDeviceVerificationStatus: async (userId: string, deviceId: string) => {
|
||||
if (deviceId === TRUSTED_DEVICE.deviceId) {
|
||||
return trustedDeviceTrustLevel;
|
||||
} else {
|
||||
return untrustedDeviceTrustLevel;
|
||||
}
|
||||
},
|
||||
// a mocked version of getEncryptionInfoForEvent which will pick its result from `eventToEncryptionInfoMap`
|
||||
getEncryptionInfoForEvent: async (event: MatrixEvent) => eventToEncryptionInfoMap.get(event.getId()!)!,
|
||||
} as unknown as CryptoApi;
|
||||
client.getCrypto = () => mockCrypto;
|
||||
});
|
||||
@ -236,9 +229,9 @@ describe("EventTile", () => {
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: UNTRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNSIGNED_DEVICE,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
@ -261,9 +254,9 @@ describe("EventTile", () => {
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: TRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
@ -275,6 +268,67 @@ describe("EventTile", () => {
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[EventShieldReason.UNKNOWN, "Unknown error"],
|
||||
[EventShieldReason.UNVERIFIED_IDENTITY, "unverified user"],
|
||||
[EventShieldReason.UNSIGNED_DEVICE, "device not verified by its owner"],
|
||||
[EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"],
|
||||
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
|
||||
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
|
||||
])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => {
|
||||
mxEvent = await mkEncryptedEvent({
|
||||
plainContent: { msgtype: "m.text", body: "msg1" },
|
||||
plainType: "m.room.message",
|
||||
user: "@alice:example.org",
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: reasonCode,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
||||
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
|
||||
expect(e2eIcons).toHaveLength(1);
|
||||
expect(e2eIcons[0].classList).toContain("mx_EventTile_e2eIcon_normal");
|
||||
expect(e2eIcons[0].getAttribute("aria-label")).toContain(expectedText);
|
||||
});
|
||||
|
||||
describe("undecryptable event", () => {
|
||||
filterConsole("Error decrypting event");
|
||||
|
||||
it("shows an undecryptable warning", async () => {
|
||||
mxEvent = mkEvent({
|
||||
type: "m.room.encrypted",
|
||||
room: room.roomId,
|
||||
user: "@alice:example.org",
|
||||
event: true,
|
||||
content: {},
|
||||
});
|
||||
|
||||
const mockCrypto = {
|
||||
decryptEvent: async (_ev): Promise<IEventDecryptionResult> => {
|
||||
throw new Error("can't decrypt");
|
||||
},
|
||||
} as CryptoBackend;
|
||||
|
||||
await mxEvent.attemptDecryption(mockCrypto);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain(
|
||||
"mx_EventTile_e2eIcon_decryption_failure",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the warning when the event is edited", async () => {
|
||||
// we start out with an event from the trusted device
|
||||
mxEvent = await mkEncryptedEvent({
|
||||
@ -284,11 +338,13 @@ describe("EventTile", () => {
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: TRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
@ -305,13 +361,14 @@ describe("EventTile", () => {
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(replacementEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: UNTRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNSIGNED_DEVICE,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
await act(async () => {
|
||||
mxEvent.makeReplaced(replacementEvent);
|
||||
flushPromises();
|
||||
rerender(<WrappedEventTile roomContext={roomContext} />);
|
||||
await flushPromises;
|
||||
});
|
||||
|
||||
// check it was updated
|
||||
@ -331,12 +388,14 @@ describe("EventTile", () => {
|
||||
user: "@alice:example.org",
|
||||
room: room.roomId,
|
||||
});
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
authenticated: true,
|
||||
sender: TRUSTED_DEVICE,
|
||||
} as IEncryptedEventInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
eventToEncryptionInfoMap.set(mxEvent.getId()!, {
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
await act(flushPromises);
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
@ -355,7 +414,8 @@ describe("EventTile", () => {
|
||||
|
||||
await act(async () => {
|
||||
mxEvent.makeReplaced(replacementEvent);
|
||||
await flushPromises();
|
||||
rerender(<WrappedEventTile roomContext={roomContext} />);
|
||||
await flushPromises;
|
||||
});
|
||||
|
||||
// check it was updated
|
||||
|
Loading…
Reference in New Issue
Block a user