Skip to content

Commit

Permalink
Merge branch 'develop' into hs/empty-topics
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot authored Feb 4, 2025
2 parents 2b8e8b3 + 1ea1d38 commit 1d22fc3
Show file tree
Hide file tree
Showing 21 changed files with 1,216 additions and 1,176 deletions.
8 changes: 2 additions & 6 deletions .stylelintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,15 @@ module.exports = {
"import-notation": null,
"value-keyword-case": null,
"declaration-block-no-redundant-longhand-properties": null,
"declaration-block-no-duplicate-properties": [
true,
// useful for fallbacks
{ ignore: ["consecutive-duplicates-with-different-values"] },
],
"shorthand-property-no-redundant-values": null,
"property-no-vendor-prefix": null,
"value-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"media-feature-name-no-vendor-prefix": null,
"number-max-precision": null,
"no-invalid-double-slash-comments": true,
"media-feature-range-notation": null,
"declaration-property-value-no-unknown": null,
"declaration-property-value-keyword-no-deprecated": null,
"csstools/value-no-unknown-custom-properties": [
true,
{
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@
"semver": "^7.5.2",
"source-map-loader": "^5.0.0",
"strip-ansi": "^7.1.0",
"stylelint": "^16.1.0",
"stylelint-config-standard": "^36.0.0",
"stylelint": "^16.13.0",
"stylelint-config-standard": "^37.0.0",
"stylelint-scss": "^6.0.0",
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
"terser-webpack-plugin": "^5.3.9",
Expand Down
96 changes: 96 additions & 0 deletions playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/

import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";

import { test, expect } from ".";
import {
checkDeviceIsConnectedKeyBackup,
checkDeviceIsCrossSigned,
createBot,
deleteCachedSecrets,
verifySession,
} from "../../crypto/utils";

test.describe("Encryption tab", () => {
test.use({
displayName: "Alice",
});

let recoveryKey: GeneratedSecretStorageKey;
let expectedBackupVersion: string;

test.beforeEach(async ({ page, homeserver, credentials }) => {
// The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
const res = await createBot(page, homeserver, credentials);
recoveryKey = res.recoveryKey;
expectedBackupVersion = res.expectedBackupVersion;
});

test(
"should show a 'Verify this device' button if the device is unverified",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
const dialog = await util.openEncryptionTab();
const content = util.getEncryptionTabContent();

// The user's device is in an unverified state, therefore the only option available to them here is to verify it
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
await expect(verifyButton).toBeVisible();
await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
await verifyButton.click();

await util.verifyDevice(recoveryKey);

await expect(content).toMatchScreenshot("default-tab.png", {
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
});

// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);

// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);

// Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
//
// This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
// We simulate this case by deleting the cached secrets in the indexedDB.
test(
"should prompt to enter the recovery key when the secrets are not cached locally",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await verifySession(app, "new passphrase");
// We need to delete the cached secrets
await deleteCachedSecrets(page);

await util.openEncryptionTab();
// We ask the user to enter the recovery key
const dialog = util.getEncryptionTabContent();
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
await expect(enterKeyButton).toBeVisible();
await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
await enterKeyButton.click();

// Fill the recovery key
await util.enterRecoveryKey(recoveryKey);
await expect(dialog).toMatchScreenshot("default-tab.png", {
mask: [dialog.getByTestId("deviceId"), dialog.getByTestId("sessionKey")],
});

// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);

// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
});
75 changes: 3 additions & 72 deletions playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,17 @@
* Please see LICENSE files in the repository root for full details.
*/

import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";

import { test, expect } from ".";
import {
checkDeviceIsConnectedKeyBackup,
checkDeviceIsCrossSigned,
createBot,
deleteCachedSecrets,
verifySession,
} from "../../crypto/utils";
import { checkDeviceIsConnectedKeyBackup, createBot, verifySession } from "../../crypto/utils";

test.describe("Recovery section in Encryption tab", () => {
test.use({
displayName: "Alice",
});

let recoveryKey: GeneratedSecretStorageKey;
let expectedBackupVersion: string;

test.beforeEach(async ({ page, homeserver, credentials }) => {
const res = await createBot(page, homeserver, credentials);
recoveryKey = res.recoveryKey;
expectedBackupVersion = res.expectedBackupVersion;
});

test("should verify the device", { tag: "@screenshot" }, async ({ page, app, util }) => {
const dialog = await util.openEncryptionTab();
const content = util.getEncryptionTabContent();

// The user's device is in an unverified state, therefore the only option available to them here is to verify it
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
await expect(verifyButton).toBeVisible();
await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
await verifyButton.click();

await util.verifyDevice(recoveryKey);

await expect(content).toMatchScreenshot("default-tab.png", {
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
});

// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);

// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
// The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
await createBot(page, homeserver, credentials);
});

test(
Expand Down Expand Up @@ -121,37 +85,4 @@ test.describe("Recovery section in Encryption tab", () => {
// Check that the current device is connected to key backup and the backup version is the expected one
await checkDeviceIsConnectedKeyBackup(app, "1", true);
});

// Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
//
// This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
// We simulate this case by deleting the cached secrets in the indexedDB.
test(
"should enter the recovery key when the secrets are not cached",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await verifySession(app, "new passphrase");
// We need to delete the cached secrets
await deleteCachedSecrets(page);

await util.openEncryptionTab();
// We ask the user to enter the recovery key
const dialog = util.getEncryptionTabContent();
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
await expect(enterKeyButton).toBeVisible();
await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("out-of-sync-recovery.png");
await enterKeyButton.click();

// Fill the recovery key
await util.enterRecoveryKey(recoveryKey);
await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");

// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);

// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion playwright/testcontainers/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverCon
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
import { Api, ClientServerApi, Verb } from "../plugins/utils/api.ts";

const TAG = "develop@sha256:e6b4c69101a0d8fd6ff6a26233eb6f92e984d578476f087c26a0fb72cddc9623";
const TAG = "develop@sha256:098126c6be750dffaff5bd19db254609aadaf34f76c70f2dca9821cb12428613";

const DEFAULT_CONFIG = {
server_name: "localhost",
Expand Down
53 changes: 50 additions & 3 deletions res/css/views/right_panel/_UserInfo.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Please see LICENSE files in the repository root for full details.
}

.mx_UserInfo_container {
padding: var(--cpd-space-4x) 0;
padding: var(--cpd-space-2x) 0 var(--cpd-space-4x);
margin: 0 var(--cpd-space-4x);

.mx_UserInfo_container_verifyButton {
Expand Down Expand Up @@ -65,7 +65,7 @@ Please see LICENSE files in the repository root for full details.
}

.mx_UserInfo_avatar {
margin: $spacing-24 $spacing-32 0 $spacing-32;
margin: var(--cpd-space-12x) var(--cpd-space-4x) 0 var(--cpd-space-4x);

.mx_UserInfo_avatar_transition {
max-width: 120px;
Expand Down Expand Up @@ -98,8 +98,18 @@ Please see LICENSE files in the repository root for full details.
margin: 5px 0;
}

.mx_UserInfo_header {
margin-bottom: var(--cpd-space-8x);
padding-bottom: 0;
}

.mx_UserInfo_profile {
display: flex;
flex-direction: column;
gap: var(--cpd-space-1x);

h1 {
margin: 0;
font-size: $font-20px;
line-height: $font-25px;

Expand All @@ -119,8 +129,45 @@ Please see LICENSE files in the repository root for full details.
}
}

.mx_UserInfo_profile_name {
height: 30px;
}

.mx_UserInfo_profile_mxid {
color: var(--cpd-color-text-secondary);
height: 28px;
}

.mx_UserInfo_profileStatus {
margin: var(--cpd-space-1x) 0;
height: 20px;
}

.mx_UserInfo_timezone {
height: 20px;
margin: 0;
display: flex;
align-items: center;
}

/** Overrides for the copy to clipboard button **/
.mx_CopyableText {
align-items: center;
}

.mx_CopyableText_copyButton {
width: 28px;
height: 28px;
display: flex;
justify-content: center;
align-items: center;
position: unset;
padding-left: var(--cpd-space-2x);
}

.mx_CopyableText_copyButton::before {
width: 20px;
height: 20px;
background-color: var(--cpd-color-icon-secondary-alpha);
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/components/views/right_panel/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { asyncSome } from "../../../utils/arrays";
import { Flex } from "../../utils/Flex";
import CopyableText from "../elements/CopyableText";
import { useUserTimezone } from "../../../hooks/useUserTimezone";

export interface IDevice extends Device {
ambiguous?: boolean;
}
Expand Down Expand Up @@ -580,8 +581,10 @@ export const warnSelfDemote = async (isSpace: boolean): Promise<boolean> => {

const Container: React.FC<{
children: ReactNode;
}> = ({ children }) => {
return <div className="mx_UserInfo_container">{children}</div>;
className?: string;
}> = ({ children, className }) => {
const classes = classNames("mx_UserInfo_container", className);
return <div className={classes}>{children}</div>;
};

interface IPowerLevelsContent {
Expand Down Expand Up @@ -1707,22 +1710,22 @@ export const UserInfoHeader: React.FC<{
</div>
</div>

<Container>
<Container className="mx_UserInfo_header">
<Flex direction="column" align="center" className="mx_UserInfo_profile">
<Heading size="sm" weight="semibold" as="h1" dir="auto">
<Flex direction="row-reverse" align="center">
<Flex className="mx_UserInfo_profile_name" direction="row-reverse" align="center">
{displayName}
{e2eIcon}
</Flex>
</Heading>
{presenceLabel}
{timezoneInfo && (
<Tooltip label={timezoneInfo?.timezone ?? ""}>
<span className="mx_UserInfo_timezone">
<Flex align="center" className="mx_UserInfo_timezone">
<Text size="sm" weight="regular">
{timezoneInfo?.friendly ?? ""}
</Text>
</span>
</Flex>
</Tooltip>
)}
<Text size="sm" weight="semibold" className="mx_UserInfo_profile_mxid">
Expand Down
Loading

0 comments on commit 1d22fc3

Please sign in to comment.