Skip to content

Commit

Permalink
Merge branch 'develop' into florianduros/encryption-tab
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Dec 19, 2024
2 parents 618557c + 2c4a079 commit c805cd8
Show file tree
Hide file tree
Showing 46 changed files with 1,042 additions and 914 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ on:
options:
- staging.element.io
- app.element.io
skip-checks:
description: Skip CI on the tagged commit
required: true
default: false
type: boolean
concurrency: ${{ inputs.site || 'staging.element.io' }}
permissions: {}
jobs:
Expand Down Expand Up @@ -75,6 +80,7 @@ jobs:

- name: Wait for other steps to succeed
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
if: inputs.skip-checks != true
with:
ref: ${{ github.sha }}
running-workflow-name: "Deploy to Cloudflare Pages"
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
Changes in [1.11.89](https://github.com/element-hq/element-web/releases/tag/v1.11.89) (2024-12-18)
==================================================================================================
This is a patch release to fix a bug which could prevent loading stored crypto state from storage, and also to fix URL previews when switching back to a room.

## 🐛 Bug Fixes

* Upgrade matrix-sdk-crypto-wasm to 1.11.0 (https://github.com/matrix-org/matrix-js-sdk/pull/4593)
* Fix url preview display ([#28766](https://github.com/element-hq/element-web/pull/28766)).


Changes in [1.11.88](https://github.com/element-hq/element-web/releases/tag/v1.11.88) (2024-12-17)
==================================================================================================
## ✨ Features
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.88",
"version": "1.11.89",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {
Expand Down Expand Up @@ -282,7 +282,7 @@
"terser-webpack-plugin": "^5.3.9",
"ts-node": "^10.9.1",
"ts-prune": "^0.10.3",
"typescript": "5.6.3",
"typescript": "5.7.2",
"util": "^0.12.5",
"web-streams-polyfill": "^4.0.0",
"webpack": "^5.89.0",
Expand Down
28 changes: 28 additions & 0 deletions playwright/e2e/crypto/backups.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Please see LICENSE files in the repository root for full details.
import { type Page } from "@playwright/test";

import { test, expect } from "../../element-web-test";
import { test as masTest, registerAccountMas } from "../oidc";
import { isDendrite } from "../../plugins/homeserver/dendrite";

async function expectBackupVersionToBe(page: Page, version: string) {
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
Expand All @@ -18,6 +20,32 @@ async function expectBackupVersionToBe(page: Page, version: string) {
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(version);
}

masTest.describe("Encryption state after registration", () => {
masTest.skip(isDendrite, "does not yet support MAS");

masTest("Key backup is enabled by default", async ({ page, mailhog, app }) => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhog.api, "alice", "[email protected]", "Pa$sW0rD!");

await app.settings.openUserSettings("Security & Privacy");
expect(page.getByText("This session is backing up your keys.")).toBeVisible();
});

masTest("user is prompted to set up recovery", async ({ page, mailhog, app }) => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhog.api, "alice", "[email protected]", "Pa$sW0rD!");

await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click();

await expect(page.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
});
});

test.describe("Backups", () => {
test.use({
displayName: "Hanako",
Expand Down
4 changes: 2 additions & 2 deletions playwright/e2e/crypto/dehydration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ Please see LICENSE files in the repository root for full details.

import { Locator, type Page } from "@playwright/test";

import { test as base, expect } from "../../element-web-test";
import { test as base, expect, Fixtures } from "../../element-web-test";
import { viewRoomSummaryByName } from "../right-panel/utils";
import { isDendrite } from "../../plugins/homeserver/dendrite";

const test = base.extend({
const test = base.extend<Fixtures>({
// eslint-disable-next-line no-empty-pattern
startHomeserverOpts: async ({}, use) => {
await use("dehydration");
Expand Down
4 changes: 2 additions & 2 deletions playwright/e2e/crypto/migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Please see LICENSE files in the repository root for full details.
import path from "path";
import { readFile } from "node:fs/promises";

import { expect, test as base } from "../../element-web-test";
import { expect, Fixtures, test as base } from "../../element-web-test";

const test = base.extend({
const test = base.extend<Fixtures>({
// Replace the `user` fixture with one which populates the indexeddb data before starting the app.
user: async ({ context, pageWithCredentials: page, credentials }, use) => {
await page.route(`/test_indexeddb_cryptostore_dump/*`, async (route, request) => {
Expand Down
4 changes: 3 additions & 1 deletion playwright/e2e/room/room-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ test.describe("Room Header", () => {

// Assert the size of buttons on RoomHeader are specified and the buttons are not compressed
// Note these assertions do not check the size of mx_LegacyRoomHeader_name button
const buttons = header.locator(".mx_Flex").getByRole("button");
const buttons = header.getByRole("button").filter({
has: page.locator("svg"),
});
await expect(buttons).toHaveCount(5);

for (const button of await buttons.all()) {
Expand Down
6 changes: 4 additions & 2 deletions playwright/element-web-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ interface CredentialsWithDisplayName extends Credentials {
displayName: string;
}

export const test = base.extend<{
export interface Fixtures {
axe: AxeBuilder;
checkA11y: () => Promise<void>;

Expand Down Expand Up @@ -124,7 +124,9 @@ export const test = base.extend<{
slidingSyncProxy: ProxyInstance;
labsFlags: string[];
webserver: Webserver;
}>({
}

export const test = base.extend<Fixtures>({
config: CONFIG_JSON,
page: async ({ context, page, config, labsFlags }, use) => {
await context.route(`http://localhost:8080/config.json*`, async (route) => {
Expand Down
2 changes: 1 addition & 1 deletion playwright/plugins/homeserver/synapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image.
// We target a specific digest as every now and then a Synapse update will break our CI.
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
const DOCKER_TAG = "develop@sha256:ef3d491214fa380918c736d9aa720992fb58829ce5c06fa3ca36d357fa1df75d";
const DOCKER_TAG = "develop@sha256:c965896a4865479ab2628807ebf6d9c742586f3b6185a56f10077a408f1c7c3b";

async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 8 additions & 7 deletions src/CreateCrossSigning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ import { _t } from "./languageHandler";
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";

/**
* Determine if the homeserver allows uploading device keys with only password auth.
* Determine if the homeserver allows uploading device keys with only password auth, or with no auth at
* all (ie. if the homeserver supports MSC3967).
* @param cli The Matrix Client to use
* @returns True if the homeserver allows uploading device keys with only password auth, otherwise false
* @returns True if the homeserver allows uploading device keys with only password auth or with no auth
* at all, otherwise false
*/
async function canUploadKeysWithPasswordOnly(cli: MatrixClient): Promise<boolean> {
try {
await cli.uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys);
// We should never get here: the server should always require
// UI auth to upload device signing keys. If we do, we upload
// no keys which would be a no-op.
logger.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!");
return false;
// If we get here, it's because the server is allowing us to upload keys without
// auth the first time due to MSC3967. Therefore, yes, we can upload keys
// (with or without password, technically, but that's fine).
return true;
} catch (error) {
if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
logger.log("uploadDeviceSigningKeys advertised no flows!");
Expand Down
28 changes: 18 additions & 10 deletions src/DeviceListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,21 +295,29 @@ export default class DeviceListener {
await crypto.getUserDeviceInfo([cli.getSafeUserId()]);

// cross signing isn't enabled - nag to enable it
// There are 2 different toasts for:
// There are 3 different toasts for:
if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
// Toast 1. Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus();
} else {
// No cross-signing or key backup on account (set up encryption)
await cli.waitForClientWellKnown();
if (isSecureBackupRequired(cli) && isLoggedIn()) {
// If we're meant to set up, and Secure Backup is required,
// trigger the flow directly without a toast once logged in.
hideSetupEncryptionToast();
accessSecretStorage();
const backupInfo = await this.getKeyBackupInfo();
if (backupInfo) {
// Toast 2: Key backup is enabled but recovery (4S) is not set up: prompt user to set up recovery.
// Since we now enable key backup at registration time, this will be the common case for
// new users.
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
} else {
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
// Toast 3: No cross-signing or key backup on account (set up encryption)
await cli.waitForClientWellKnown();
if (isSecureBackupRequired(cli) && isLoggedIn()) {
// If we're meant to set up, and Secure Backup is required,
// trigger the flow directly without a toast once logged in.
hideSetupEncryptionToast();
accessSecretStorage();
} else {
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
}
}
}
}
Expand Down
26 changes: 19 additions & 7 deletions src/components/views/dialogs/LogoutDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ enum BackupStatus {
/** there is a backup on the server but we are not backing up to it */
SERVER_BACKUP_BUT_DISABLED,

/** Key backup is set up but recovery (4s) is not */
BACKUP_NO_RECOVERY,

/** backup is not set up locally and there is no backup on the server */
NO_BACKUP,

Expand Down Expand Up @@ -104,7 +107,11 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
}

if ((await crypto.getActiveSessionBackupVersion()) !== null) {
this.setState({ backupStatus: BackupStatus.BACKUP_ACTIVE });
if (await crypto.isSecretStorageReady()) {
this.setState({ backupStatus: BackupStatus.BACKUP_ACTIVE });
} else {
this.setState({ backupStatus: BackupStatus.BACKUP_NO_RECOVERY });
}
return;
}

Expand Down Expand Up @@ -164,13 +171,17 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
};

/**
* Show a dialog prompting the user to set up key backup.
* Show a dialog prompting the user to set up their recovery method.
*
* Either:
* * There is no backup at all ({@link BackupStatus.NO_BACKUP})
* * There is a backup set up but recovery (4s) is not ({@link BackupStatus.BACKUP_NO_RECOVERY})
* * There is a backup on the server but we are not connected to it ({@link BackupStatus.SERVER_BACKUP_BUT_DISABLED})
* * We were unable to pull the backup data ({@link BackupStatus.ERROR}).
*
* Either there is no backup at all ({@link BackupStatus.NO_BACKUP}), there is a backup on the server but
* we are not connected to it ({@link BackupStatus.SERVER_BACKUP_BUT_DISABLED}), or we were unable to pull the
* backup data ({@link BackupStatus.ERROR}). In all three cases, we should prompt the user to set up key backup.
* In all four cases, we should prompt the user to set up a method of recovery.
*/
private renderSetupBackupDialog(): React.ReactNode {
private renderSetupRecoveryMethod(): React.ReactNode {
const description = (
<div>
<p>{_t("auth|logout_dialog|setup_secure_backup_description_1")}</p>
Expand Down Expand Up @@ -254,7 +265,8 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
case BackupStatus.NO_BACKUP:
case BackupStatus.SERVER_BACKUP_BUT_DISABLED:
case BackupStatus.ERROR:
return this.renderSetupBackupDialog();
case BackupStatus.BACKUP_NO_RECOVERY:
return this.renderSetupRecoveryMethod();
}
}
}
3 changes: 2 additions & 1 deletion src/components/views/messages/TextualBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
if (!this.props.editState) {
const stoppedEditing = prevProps.editState && !this.props.editState;
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
if (messageWasEdited || stoppedEditing) {
const urlPreviewChanged = prevProps.showUrlPreview !== this.props.showUrlPreview;
if (messageWasEdited || stoppedEditing || urlPreviewChanged) {
this.applyFormatting();
}
}
Expand Down
Loading

0 comments on commit c805cd8

Please sign in to comment.