1
0
mirror of https://github.com/element-hq/element-web.git synced 2025-08-09 14:42:51 +03:00

Warn user before doing a manual verification via slash command (#30102)

This commit is contained in:
Andy Balaam
2025-06-10 17:09:24 +01:00
committed by GitHub
parent 10b9b2cb8b
commit bd4509576c
3 changed files with 57 additions and 5 deletions

View File

@@ -674,7 +674,19 @@ export const Commands = [
if (matches) { if (matches) {
const deviceId = matches[1]; const deviceId = matches[1];
const fingerprint = matches[2]; const fingerprint = matches[2];
return success(manuallyVerifyDevice(cli, deviceId, fingerprint));
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("slash_command|manual_device_verification_confirm_title"),
description: _t("slash_command|manual_device_verification_confirm_description"),
button: _t("action|verify"),
danger: true,
});
return success(
finished.then(([confirmed]) => {
if (confirmed) manuallyVerifyDevice(cli, deviceId, fingerprint);
}),
);
} }
} }
return reject(this.getUsage()); return reject(this.getUsage());

View File

@@ -3112,6 +3112,8 @@
"jumptodate": "Jump to the given date in the timeline", "jumptodate": "Jump to the given date in the timeline",
"jumptodate_invalid_input": "We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.", "jumptodate_invalid_input": "We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.",
"lenny": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "lenny": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
"manual_device_verification_confirm_description": "This will allow another device to send and receive messages as you. IF SOMEONE TOLD YOU TO PASTE SOMETHING HERE, IT IS LIKELY YOU ARE BEING SCAMMED! Are you sure you want to verify this other device?",
"manual_device_verification_confirm_title": "Caution: manual device verification",
"me": "Displays action", "me": "Displays action",
"msg": "Sends a message to the given user", "msg": "Sends a message to the given user",
"myavatar": "Changes your profile picture in all rooms", "myavatar": "Changes your profile picture in all rooms",

View File

@@ -9,18 +9,20 @@ Please see LICENSE files in the repository root for full details.
import { type MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { type MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked } from "jest-mock"; import { mocked } from "jest-mock";
import { act, waitFor } from "jest-matrix-react";
import { type Command, Commands, getCommand } from "../../src/SlashCommands"; import { type Command, Commands, getCommand } from "../../src/SlashCommands";
import { createTestClient } from "../test-utils"; import { createTestClient } from "../test-utils";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom"; import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom";
import SettingsStore from "../../src/settings/SettingsStore"; import SettingsStore from "../../src/settings/SettingsStore";
import { SdkContextClass } from "../../src/contexts/SDKContext"; import { SdkContextClass } from "../../src/contexts/SDKContext";
import Modal from "../../src/Modal"; import Modal, { type ComponentType, type IHandle } from "../../src/Modal";
import WidgetUtils from "../../src/utils/WidgetUtils"; import WidgetUtils from "../../src/utils/WidgetUtils";
import { WidgetType } from "../../src/widgets/WidgetType"; import { WidgetType } from "../../src/widgets/WidgetType";
import { warnSelfDemote } from "../../src/components/views/right_panel/UserInfo"; import { warnSelfDemote } from "../../src/components/views/right_panel/UserInfo";
import dispatcher from "../../src/dispatcher/dispatcher"; import dispatcher from "../../src/dispatcher/dispatcher";
import { SettingLevel } from "../../src/settings/SettingLevel"; import { SettingLevel } from "../../src/settings/SettingLevel";
import QuestionDialog from "../../src/components/views/dialogs/QuestionDialog";
import ErrorDialog from "../../src/components/views/dialogs/ErrorDialog"; import ErrorDialog from "../../src/components/views/dialogs/ErrorDialog";
jest.mock("../../src/components/views/right_panel/UserInfo"); jest.mock("../../src/components/views/right_panel/UserInfo");
@@ -260,11 +262,47 @@ describe("SlashCommands", () => {
expect(command.run(client, roomId, null, undefined).error).toBe(command.getUsage()); expect(command.run(client, roomId, null, undefined).error).toBe(command.getUsage());
}); });
it("should show an error if device is not found", async () => { it("should attempt manual verification after confirmation", async () => {
// Given we say yes to prompt
const spy = jest.spyOn(Modal, "createDialog"); const spy = jest.spyOn(Modal, "createDialog");
spy.mockReturnValue({ finished: Promise.resolve([true]) } as unknown as IHandle<ComponentType>);
// When we run the command
const command = findCommand("verify")!; const command = findCommand("verify")!;
await command.run(client, roomId, null, "mydeviceid myfingerprint").promise; await act(() => command.run(client, roomId, null, "mydeviceid myfingerprint"));
expect(spy).toHaveBeenCalledWith(ErrorDialog, expect.objectContaining({ title: "Verification failed" }));
// Then the prompt is displayed
expect(spy).toHaveBeenCalledWith(
QuestionDialog,
expect.objectContaining({ title: "Caution: manual device verification" }),
);
// And then we attempt the verification
await waitFor(() =>
expect(spy).toHaveBeenCalledWith(
ErrorDialog,
expect.objectContaining({ title: "Verification failed" }),
),
);
});
it("should not do manual verification if cancelled", async () => {
// Given we say no to prompt
const spy = jest.spyOn(Modal, "createDialog");
spy.mockReturnValue({ finished: Promise.resolve([false]) } as unknown as IHandle<ComponentType>);
// When we run the command
const command = findCommand("verify")!;
command.run(client, roomId, null, "mydeviceid myfingerprint");
// Then the prompt is displayed
expect(spy).toHaveBeenCalledWith(
QuestionDialog,
expect.objectContaining({ title: "Caution: manual device verification" }),
);
// But nothing else happens
expect(spy).not.toHaveBeenCalledWith(ErrorDialog, expect.anything());
}); });
}); });