Merge branch 'develop' into t3chguy/fix/25969

This commit is contained in:
Michael Telatynski 2024-12-03 18:09:29 +00:00 committed by GitHub
commit 2ce3dac1a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 299 additions and 549 deletions

View file

@ -10,24 +10,29 @@ inputs:
runs:
using: composite
steps:
- name: Download current version for its old bundles
id: current_download
- name: Download release tarball
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
with:
tag: steps.current_version.outputs.version
tag: ${{ inputs.tag }}
fileName: element-*.tar.gz*
out-file-path: ${{ runner.temp }}/download-verify-element-tarball
- name: Verify tarball
shell: bash
run: gpg --verify element-*.tar.gz.asc element-*.tar.gz
working-directory: ${{ runner.temp }}/download-verify-element-tarball
- name: Extract tarball
run: tar xvzf element-*.tar.gz -C webapp --strip-components=1
shell: bash
run: |
mkdir webapp
tar xvzf element-*.tar.gz -C webapp --strip-components=1
working-directory: ${{ runner.temp }}/download-verify-element-tarball
- name: Move webapp to out-file-path
shell: bash
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}
- name: Clean up temp directory
shell: bash
run: rm -R ${{ runner.temp }}/download-verify-element-tarball

View file

@ -1,7 +1,8 @@
# Manual deploy workflow for deploying to app.element.io & staging.element.io
# Runs automatically for staging.element.io when an RC or Release is published
# Note: Does *NOT* run automatically for app.element.io so that it gets tested on staging.element.io beforehand
name: Build and Deploy ${{ inputs.site || 'staging.element.io' }}
name: Deploy release
run-name: Deploy ${{ github.ref_name }} to ${{ inputs.site || 'staging.element.io' }}
on:
release:
types: [published]
@ -28,37 +29,40 @@ jobs:
env:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- uses: actions/checkout@v4
- name: Load GPG key
run: |
curl https://packages.element.io/element-release-key.gpg | gpg --import
gpg -k "$GPG_FINGERPRINT"
env:
GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}
GPG_FINGERPRINT: ${{ vars.GPG_FINGERPRINT }}
- name: Check current version on deployment
id: current_version
run: |
echo "version=$(curl -s https://$SITE/version)" >> $GITHUB_OUTPUT
version=$(curl -s https://$SITE/version)
echo "version=${version#v}" >> $GITHUB_OUTPUT
# The current version bundle melding dance is skipped if the version we're deploying is the same
# as then we're just doing a re-deploy of the same version with potentially different configs.
- name: Download current version for its old bundles
id: current_download
if: steps.current_version.outputs.version != github.ref_name
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
uses: ./.github/actions/download-verify-element-tarball
with:
tag: steps.current_version.outputs.version
out-file-path: current_version
tag: v${{ steps.current_version.outputs.version }}
out-file-path: _current_version
- name: Download target version
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
uses: ./.github/actions/download-verify-element-tarball
with:
tag: ${{ github.ref_name }}
out-file-path: _deploy
- name: Merge current bundles into target
if: steps.current_download.outcome == 'success'
run: cp -vnpr current_version/bundles/* _deploy/bundles/
run: cp -vnpr _current_version/bundles/* _deploy/bundles/
- name: Copy config
run: cp element.io/app/config.json _deploy/config.json
@ -73,7 +77,7 @@ jobs:
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: ${{ github.sha }}
running-workflow-name: "Build and Deploy ${{ env.SITE }}"
running-workflow-name: "Deploy to Cloudflare Pages"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$

View file

@ -1,3 +1,33 @@
Changes in [1.11.87](https://github.com/element-hq/element-web/releases/tag/v1.11.87) (2024-12-03)
==================================================================================================
## ✨ Features
* Send and respect MSC4230 is\_animated flag ([#28513](https://github.com/element-hq/element-web/pull/28513)). Contributed by @t3chguy.
* Display a warning when an unverified user's identity changes ([#28211](https://github.com/element-hq/element-web/pull/28211)). Contributed by @uhoreg.
* Swap out Twitter link for Mastodon on auth footer ([#28508](https://github.com/element-hq/element-web/pull/28508)). Contributed by @t3chguy.
* Consider `org.matrix.msc3417.call` as video room in create room dialog ([#28497](https://github.com/element-hq/element-web/pull/28497)). Contributed by @t3chguy.
* Standardise icons using Compound Design Tokens ([#28217](https://github.com/element-hq/element-web/pull/28217)). Contributed by @t3chguy.
* Start sending stable `m.marked_unread` events ([#28478](https://github.com/element-hq/element-web/pull/28478)). Contributed by @tulir.
* Upgrade to compound-design-tokens v2 ([#28471](https://github.com/element-hq/element-web/pull/28471)). Contributed by @t3chguy.
* Standardise icons using Compound Design Tokens ([#28286](https://github.com/element-hq/element-web/pull/28286)). Contributed by @t3chguy.
* Remove reply fallbacks as per merged MSC2781 ([#28406](https://github.com/element-hq/element-web/pull/28406)). Contributed by @t3chguy.
* Use React Suspense when rendering async modals ([#28386](https://github.com/element-hq/element-web/pull/28386)). Contributed by @t3chguy.
## 🐛 Bug Fixes
* Add spinner when room encryption is loading in room settings ([#28535](https://github.com/element-hq/element-web/pull/28535)). Contributed by @florianduros.
* Fix getOidcCallbackUrl for Element Desktop ([#28521](https://github.com/element-hq/element-web/pull/28521)). Contributed by @t3chguy.
* Filter out redacted poll votes to avoid crashing the Poll widget ([#28498](https://github.com/element-hq/element-web/pull/28498)). Contributed by @t3chguy.
* Fix force tab complete not working since switching to React 18 createRoot API ([#28505](https://github.com/element-hq/element-web/pull/28505)). Contributed by @t3chguy.
* Fix media captions in bubble layout ([#28480](https://github.com/element-hq/element-web/pull/28480)). Contributed by @tulir.
* Reset cross-signing before backup when resetting both ([#28402](https://github.com/element-hq/element-web/pull/28402)). Contributed by @uhoreg.
* Listen to events so that encryption icon updates when status changes ([#28407](https://github.com/element-hq/element-web/pull/28407)). Contributed by @uhoreg.
* Check that the file the user chose has a MIME type of `image/*` ([#28467](https://github.com/element-hq/element-web/pull/28467)). Contributed by @t3chguy.
* Fix download button size in message action bar ([#28472](https://github.com/element-hq/element-web/pull/28472)). Contributed by @t3chguy.
* Allow tab completing users in brackets ([#28460](https://github.com/element-hq/element-web/pull/28460)). Contributed by @t3chguy.
* Fix React 18 strict mode breaking spotlight dialog ([#28452](https://github.com/element-hq/element-web/pull/28452)). Contributed by @MidhunSureshR.
Changes in [1.11.86](https://github.com/element-hq/element-web/releases/tag/v1.11.86) (2024-11-19)
==================================================================================================
## ✨ Features

View file

@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.86",
"version": "1.11.87",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {

View file

@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
*/
import { lazy } from "react";
import { ICryptoCallbacks, SecretStorage } from "matrix-js-sdk/src/matrix";
import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
import { SecretStorage } from "matrix-js-sdk/src/matrix";
import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey, CryptoCallbacks } from "matrix-js-sdk/src/crypto-api";
import { logger } from "matrix-js-sdk/src/logger";
import Modal from "./Modal";
@ -159,7 +159,7 @@ function cacheSecretStorageKey(
}
}
export const crossSigningCallbacks: ICryptoCallbacks = {
export const crossSigningCallbacks: CryptoCallbacks = {
getSecretStorageKey,
cacheSecretStorageKey,
};

View file

@ -49,7 +49,6 @@ import VoipUserMapper from "./VoipUserMapper";
import { htmlSerializeFromMdIfNeeded } from "./editor/serialize";
import { leaveRoomBehaviour } from "./utils/leave-behaviour";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { getDeviceCryptoInfo } from "./utils/crypto/deviceInfo";
import { isCurrentLocalRoom, reject, singleMxcUpload, success, successSync } from "./slash-commands/utils";
import { deop, op } from "./slash-commands/op";
import { CommandCategories } from "./slash-commands/interface";
@ -658,69 +657,6 @@ export const Commands = [
category: CommandCategories.admin,
renderingTypes: [TimelineRenderingType.Room],
}),
new Command({
command: "verify",
args: "<user-id> <device-id> <device-signing-key>",
description: _td("slash_command|verify"),
runFn: function (cli, roomId, threadId, args) {
if (args) {
const matches = args.match(/^(\S+) +(\S+) +(\S+)$/);
if (matches) {
const userId = matches[1];
const deviceId = matches[2];
const fingerprint = matches[3];
return success(
(async (): Promise<void> => {
const device = await getDeviceCryptoInfo(cli, userId, deviceId);
if (!device) {
throw new UserFriendlyError("slash_command|verify_unknown_pair", {
userId,
deviceId,
cause: undefined,
});
}
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, deviceId);
if (deviceTrust?.isVerified()) {
if (device.getFingerprint() === fingerprint) {
throw new UserFriendlyError("slash_command|verify_nop");
} else {
throw new UserFriendlyError("slash_command|verify_nop_warning_mismatch");
}
}
if (device.getFingerprint() !== fingerprint) {
const fprint = device.getFingerprint();
throw new UserFriendlyError("slash_command|verify_mismatch", {
fprint,
userId,
deviceId,
fingerprint,
cause: undefined,
});
}
await cli.setDeviceVerified(userId, deviceId, true);
// Tell the user we verified everything
Modal.createDialog(InfoDialog, {
title: _t("slash_command|verify_success_title"),
description: (
<div>
<p>{_t("slash_command|verify_success_description", { userId, deviceId })}</p>
</div>
),
});
})(),
);
}
}
return reject(this.getUsage());
},
category: CommandCategories.advanced,
renderingTypes: [TimelineRenderingType.Room],
}),
new Command({
command: "discardsession",
description: _td("slash_command|discardsession"),

View file

@ -119,6 +119,7 @@ export default class BaseDialog extends React.Component<IProps> {
<AccessibleButton
onClick={this.onCancelClick}
className="mx_Dialog_cancelButton"
title={_t("action|close")}
aria-label={_t("dialog_close_label")}
placement="bottom"
/>

View file

@ -1,90 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2017 Vector Creations Ltd
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React, { useCallback } from "react";
import { Device } from "matrix-js-sdk/src/matrix";
import * as FormattingUtils from "../../../utils/FormattingUtils";
import { _t } from "../../../languageHandler";
import QuestionDialog from "./QuestionDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IManualDeviceKeyVerificationDialogProps {
userId: string;
device: Device;
onFinished(confirm?: boolean): void;
}
export function ManualDeviceKeyVerificationDialog({
userId,
device,
onFinished,
}: IManualDeviceKeyVerificationDialogProps): JSX.Element {
const mxClient = MatrixClientPeg.safeGet();
const onLegacyFinished = useCallback(
(confirm: boolean) => {
if (confirm) {
mxClient.setDeviceVerified(userId, device.deviceId, true);
}
onFinished(confirm);
},
[mxClient, userId, device, onFinished],
);
let text;
if (mxClient?.getUserId() === userId) {
text = _t("encryption|verification|manual_device_verification_self_text");
} else {
text = _t("encryption|verification|manual_device_verification_user_text");
}
const fingerprint = device.getFingerprint();
const key = fingerprint && FormattingUtils.formatCryptoKey(fingerprint);
const body = (
<div>
<p>{text}</p>
<div className="mx_DeviceVerifyDialog_cryptoSection">
<ul>
<li>
<label>{_t("encryption|verification|manual_device_verification_device_name_label")}:</label>{" "}
<span>{device.displayName}</span>
</li>
<li>
<label>{_t("encryption|verification|manual_device_verification_device_id_label")}:</label>{" "}
<span>
<code>{device.deviceId}</code>
</span>
</li>
<li>
<label>{_t("encryption|verification|manual_device_verification_device_key_label")}:</label>{" "}
<span>
<code>
<strong>{key}</strong>
</code>
</span>
</li>
</ul>
</div>
<p>{_t("encryption|verification|manual_device_verification_footer")}</p>
</div>
);
return (
<QuestionDialog
title={_t("settings|sessions|verify_session")}
description={body}
button={_t("settings|sessions|verify_session")}
onFinished={onLegacyFinished}
/>
);
}

View file

@ -17,9 +17,20 @@ import BaseDialog from "./BaseDialog";
import { IDevice } from "../right_panel/UserInfo";
interface IProps {
/**
* The user whose device is untrusted.
*/
user: User;
/**
* The device that is untrusted.
*/
device: IDevice;
onFinished(mode?: "legacy" | "sas" | false): void;
/**
* Callback for when the dialog is dismissed.
* If mode is "sas", the user wants to verify the device with SAS. Otherwise, the dialog was dismissed normally.
* @param mode The mode of dismissal.
*/
onFinished(mode?: "sas"): void;
}
const UntrustedDeviceDialog: React.FC<IProps> = ({ device, user, onFinished }) => {
@ -56,13 +67,10 @@ const UntrustedDeviceDialog: React.FC<IProps> = ({ device, user, onFinished }) =
<p>{askToVerifyText}</p>
</div>
<div className="mx_Dialog_buttons">
<AccessibleButton kind="primary_outline" onClick={() => onFinished("legacy")}>
{_t("encryption|udd|manual_verification_button")}
</AccessibleButton>
<AccessibleButton kind="primary_outline" onClick={() => onFinished("sas")}>
{_t("encryption|udd|interactive_verification_button")}
</AccessibleButton>
<AccessibleButton kind="primary" onClick={() => onFinished(false)}>
<AccessibleButton kind="primary" onClick={() => onFinished()}>
{_t("action|done")}
</AccessibleButton>
</div>

View file

@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { CryptoEvent, MatrixClient, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { MatrixClient, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { useEffect, useMemo, useState } from "react";
import { throttle } from "lodash";
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
import { E2EStatus, shieldStatusForRoom } from "../utils/ShieldUtils";
import { useTypedEventEmitter } from "./useEventEmitter";

View file

@ -922,7 +922,6 @@
},
"udd": {
"interactive_verification_button": "Interactively verify by emoji",
"manual_verification_button": "Manually verify by text",
"other_ask_verify_text": "Ask this user to verify their session, or manually verify it below.",
"other_new_session_text": "%(name)s (%(userId)s) signed in to a new session without verifying it:",
"own_ask_verify_text": "Verify your other session using one of the options below.",
@ -957,12 +956,6 @@
"incoming_sas_dialog_waiting": "Waiting for partner to confirm…",
"incoming_sas_user_dialog_text_1": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
"incoming_sas_user_dialog_text_2": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.",
"manual_device_verification_device_id_label": "Session ID",
"manual_device_verification_device_key_label": "Session key",
"manual_device_verification_device_name_label": "Session name",
"manual_device_verification_footer": "If they don't match, the security of your communication may be compromised.",
"manual_device_verification_self_text": "Confirm by comparing the following with the User Settings in your other session:",
"manual_device_verification_user_text": "Confirm this user's session by comparing the following with their User Settings:",
"no_key_or_device": "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.",
"no_support_qr_emoji": "The device you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
"other_party_cancelled": "The other party cancelled the verification.",
@ -3036,13 +3029,6 @@
"upgraderoom": "Upgrades a room to a new version",
"upgraderoom_permission_error": "You do not have the required permissions to use this command.",
"usage": "Usage",
"verify": "Verifies a user, session, and pubkey tuple",
"verify_mismatch": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!",
"verify_nop": "Session already verified!",
"verify_nop_warning_mismatch": "WARNING: session already verified, but keys do NOT MATCH!",
"verify_success_description": "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.",
"verify_success_title": "Verified key",
"verify_unknown_pair": "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)",
"view": "Views room with given address",
"whois": "Displays information about a user"
},

View file

@ -9,7 +9,8 @@ Please see LICENSE files in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { Method, MatrixClient, Crypto } from "matrix-js-sdk/src/matrix";
import { Method, MatrixClient } from "matrix-js-sdk/src/matrix";
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
import type * as Pako from "pako";
import { MatrixClientPeg } from "../MatrixClientPeg";
@ -169,7 +170,7 @@ async function collectSynapseSpecific(client: MatrixClient, body: FormData): Pro
/**
* Collects crypto related information.
*/
async function collectCryptoInfo(cryptoApi: Crypto.CryptoApi, body: FormData): Promise<void> {
async function collectCryptoInfo(cryptoApi: CryptoApi, body: FormData): Promise<void> {
body.append("crypto_version", cryptoApi.getVersion());
const ownDeviceKeys = await cryptoApi.getOwnDeviceKeys();
@ -198,7 +199,7 @@ async function collectCryptoInfo(cryptoApi: Crypto.CryptoApi, body: FormData): P
/**
* Collects information about secret storage and backup.
*/
async function collectRecoveryInfo(client: MatrixClient, cryptoApi: Crypto.CryptoApi, body: FormData): Promise<void> {
async function collectRecoveryInfo(client: MatrixClient, cryptoApi: CryptoApi, body: FormData): Promise<void> {
const secretStorage = client.secretStorage;
body.append("secret_storage_ready", String(await cryptoApi.isSecretStorageReady()));
body.append("secret_storage_key_in_account", String(await secretStorage.hasKey()));

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { Crypto } from "matrix-js-sdk/src/matrix";
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
import { MatrixClientPeg } from "../../MatrixClientPeg";
@ -21,7 +21,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
*
* Dehydration can currently only be enabled by setting a flag in the .well-known file.
*/
async function deviceDehydrationEnabled(crypto: Crypto.CryptoApi | undefined): Promise<boolean> {
async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise<boolean> {
if (!crypto) {
return false;
}

View file

@ -15,7 +15,6 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import { accessSecretStorage } from "./SecurityManager";
import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog";
import { IDevice } from "./components/views/right_panel/UserInfo";
import { ManualDeviceKeyVerificationDialog } from "./components/views/dialogs/ManualDeviceKeyVerificationDialog";
import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState";
import { findDMForUser } from "./utils/dm/findDMForUser";
@ -53,11 +52,6 @@ export async function verifyDevice(matrixClient: MatrixClient, user: User, devic
.getCrypto()
?.requestDeviceVerification(user.userId, device.deviceId);
setRightPanel({ member: user, verificationRequestPromise });
} else if (action === "legacy") {
Modal.createDialog(ManualDeviceKeyVerificationDialog, {
userId: user.userId,
device,
});
}
},
});

View file

@ -1,104 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, screen } from "jest-matrix-react";
import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
import { stubClient } from "../../../../test-utils";
import { ManualDeviceKeyVerificationDialog } from "../../../../../src/components/views/dialogs/ManualDeviceKeyVerificationDialog";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
describe("ManualDeviceKeyVerificationDialog", () => {
let mockClient: MatrixClient;
function renderDialog(userId: string, device: Device, onLegacyFinished: (confirm: boolean) => void) {
return render(
<MatrixClientContext.Provider value={mockClient}>
<ManualDeviceKeyVerificationDialog userId={userId} device={device} onFinished={onLegacyFinished} />
</MatrixClientContext.Provider>,
);
}
beforeEach(() => {
mockClient = stubClient();
});
it("should display the device", () => {
// When
const deviceId = "XYZ";
const device = new Device({
userId: mockClient.getUserId()!,
deviceId,
displayName: "my device",
algorithms: [],
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
});
const { container } = renderDialog(mockClient.getUserId()!, device, jest.fn());
// Then
expect(container).toMatchSnapshot();
});
it("should display the device of another user", () => {
// When
const userId = "@alice:example.com";
const deviceId = "XYZ";
const device = new Device({
userId,
deviceId,
displayName: "my device",
algorithms: [],
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
});
const { container } = renderDialog(userId, device, jest.fn());
// Then
expect(container).toMatchSnapshot();
});
it("should call onFinished and matrixClient.setDeviceVerified", () => {
// When
const deviceId = "XYZ";
const device = new Device({
userId: mockClient.getUserId()!,
deviceId,
displayName: "my device",
algorithms: [],
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
});
const onFinished = jest.fn();
renderDialog(mockClient.getUserId()!, device, onFinished);
screen.getByRole("button", { name: "Verify session" }).click();
// Then
expect(onFinished).toHaveBeenCalledWith(true);
expect(mockClient.setDeviceVerified).toHaveBeenCalledWith(mockClient.getUserId(), deviceId, true);
});
it("should call onFinished and not matrixClient.setDeviceVerified", () => {
// When
const deviceId = "XYZ";
const device = new Device({
userId: mockClient.getUserId()!,
deviceId,
displayName: "my device",
algorithms: [],
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
});
const onFinished = jest.fn();
renderDialog(mockClient.getUserId()!, device, onFinished);
screen.getByRole("button", { name: "Cancel" }).click();
// Then
expect(onFinished).toHaveBeenCalledWith(false);
expect(mockClient.setDeviceVerified).not.toHaveBeenCalled();
});
});

View file

@ -0,0 +1,59 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { Device, MatrixClient, User } from "matrix-js-sdk/src/matrix";
import { render, screen } from "jest-matrix-react";
import { stubClient } from "../../../../test-utils";
import UntrustedDeviceDialog from "../../../../../src/components/views/dialogs/UntrustedDeviceDialog.tsx";
describe("<UntrustedDeviceDialog />", () => {
let client: MatrixClient;
let user: User;
let device: Device;
const onFinished = jest.fn();
beforeEach(() => {
client = stubClient();
user = User.createUser("@alice:example.org", client);
user.setDisplayName("Alice");
device = new Device({ deviceId: "device_id", userId: user.userId, algorithms: [], keys: new Map() });
});
afterEach(() => {
onFinished.mockReset();
});
function renderComponent() {
return render(<UntrustedDeviceDialog user={user} device={device} onFinished={onFinished} />);
}
it("should display the dialog for the device of another user", () => {
const { asFragment } = renderComponent();
expect(asFragment()).toMatchSnapshot();
});
it("should display the dialog for the device of the current user", () => {
jest.spyOn(client, "getUserId").mockReturnValue(user.userId);
const { asFragment } = renderComponent();
expect(asFragment()).toMatchSnapshot();
});
it("should call onFinished without parameter when Done is clicked", () => {
renderComponent();
screen.getByRole("button", { name: "Done" }).click();
expect(onFinished).toHaveBeenCalledWith();
});
it("should call onFinished with sas when Interactively verify by emoji is clicked", () => {
renderComponent();
screen.getByRole("button", { name: "Interactively verify by emoji" }).click();
expect(onFinished).toHaveBeenCalledWith("sas");
});
});

View file

@ -1,231 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ManualDeviceKeyVerificationDialog should display the device 1`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_QuestionDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Verify session
</h1>
</div>
<div
class="mx_Dialog_content"
id="mx_Dialog_content"
>
<div>
<p>
Confirm by comparing the following with the User Settings in your other session:
</p>
<div
class="mx_DeviceVerifyDialog_cryptoSection"
>
<ul>
<li>
<label>
Session name
:
</label>
<span>
my device
</span>
</li>
<li>
<label>
Session ID
:
</label>
<span>
<code>
XYZ
</code>
</span>
</li>
<li>
<label>
Session key
:
</label>
<span>
<code>
<strong>
ABCD EFGH
</strong>
</code>
</span>
</li>
</ul>
</div>
<p>
If they don't match, the security of your communication may be compromised.
</p>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Verify session
</button>
</span>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
exports[`ManualDeviceKeyVerificationDialog should display the device of another user 1`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_QuestionDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Verify session
</h1>
</div>
<div
class="mx_Dialog_content"
id="mx_Dialog_content"
>
<div>
<p>
Confirm this user's session by comparing the following with their User Settings:
</p>
<div
class="mx_DeviceVerifyDialog_cryptoSection"
>
<ul>
<li>
<label>
Session name
:
</label>
<span>
my device
</span>
</li>
<li>
<label>
Session ID
:
</label>
<span>
<code>
XYZ
</code>
</span>
</li>
<li>
<label>
Session key
:
</label>
<span>
<code>
<strong>
ABCD EFGH
</strong>
</code>
</span>
</li>
</ul>
</div>
<p>
If they don't match, the security of your communication may be compromised.
</p>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Verify session
</button>
</span>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;

View file

@ -0,0 +1,149 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<UntrustedDeviceDialog /> should display the dialog for the device of another user 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_UntrustedDeviceDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
<div
class="mx_E2EIcon mx_E2EIcon_warning"
style="width: 24px; height: 24px;"
/>
Not Trusted
</h1>
</div>
<div
class="mx_Dialog_content"
id="mx_Dialog_content"
>
<p>
Alice (@alice:example.org) signed in to a new session without verifying it:
</p>
<p>
(device_id)
</p>
<p>
Ask this user to verify their session, or manually verify it below.
</p>
</div>
<div
class="mx_Dialog_buttons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="0"
>
Interactively verify by emoji
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Done
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<UntrustedDeviceDialog /> should display the dialog for the device of the current user 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_UntrustedDeviceDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
<div
class="mx_E2EIcon mx_E2EIcon_warning"
style="width: 24px; height: 24px;"
/>
Not Trusted
</h1>
</div>
<div
class="mx_Dialog_content"
id="mx_Dialog_content"
>
<p>
You signed in to a new session without verifying it:
</p>
<p>
(device_id)
</p>
<p>
Verify your other session using one of the options below.
</p>
</div>
<div
class="mx_Dialog_buttons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="0"
>
Interactively verify by emoji
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Done
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

View file

@ -9,7 +9,8 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { screen, fireEvent, render, waitFor, act } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { Crypto, IMegolmSessionData } from "matrix-js-sdk/src/matrix";
import { IMegolmSessionData } from "matrix-js-sdk/src/matrix";
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
import ExportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ExportE2eKeysDialog";
@ -62,7 +63,7 @@ describe("ExportE2eKeysDialog", () => {
cli.getCrypto = () => {
return {
exportRoomKeysAsJson,
} as unknown as Crypto.CryptoApi;
} as unknown as CryptoApi;
};
// Mock the result of encrypting the sessions. If we don't do this, the

View file

@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { fireEvent, render, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { Crypto } from "matrix-js-sdk/src/matrix";
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
import ImportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ImportE2eKeysDialog";
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
@ -67,7 +67,7 @@ describe("ImportE2eKeysDialog", () => {
cli.getCrypto = () => {
return {
importRoomKeysAsJson,
} as unknown as Crypto.CryptoApi;
} as unknown as CryptoApi;
};
// Mock the result of decrypting the sessions, to avoid needing to

View file

@ -88,7 +88,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
</p>
</div>
<button
aria-labelledby=":r6s:"
aria-labelledby=":r74:"
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
data-testid="base-card-close-button"
role="button"
@ -402,7 +402,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
</p>
</div>
<button
aria-labelledby=":r92:"
aria-labelledby=":r9a:"
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
data-testid="base-card-close-button"
role="button"

View file

@ -47,7 +47,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
>
<button
aria-labelledby=":r154:"
aria-labelledby=":r16c:"
class="_icon-button_bh2qc_17"
role="button"
style="--cpd-icon-button-size: 32px;"
@ -73,7 +73,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
<button
aria-disabled="true"
aria-label="There's no one here to call"
aria-labelledby=":r159:"
aria-labelledby=":r16h:"
class="_icon-button_bh2qc_17"
role="button"
style="--cpd-icon-button-size: 32px;"
@ -98,7 +98,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
</button>
<button
aria-label="Room info"
aria-labelledby=":r15e:"
aria-labelledby=":r16m:"
class="_icon-button_bh2qc_17"
role="button"
style="--cpd-icon-button-size: 32px;"
@ -123,7 +123,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
</button>
<button
aria-label="Threads"
aria-labelledby=":r15j:"
aria-labelledby=":r16r:"
class="_icon-button_bh2qc_17"
role="button"
style="--cpd-icon-button-size: 32px;"

View file

@ -8251,8 +8251,8 @@ matrix-events-sdk@0.0.1:
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "34.12.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/544ac86d2080da8e55d0b727cae826e42600c490"
version "34.13.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c4ea57d42dcf8bd04c40feaa2c686487dbcab338"
dependencies:
"@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0"