Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Support Matrix 1.1 (drop legacy r0 versions) #9819

Merged
merged 28 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
640a020
Intercept "hs too old" error and replace with a user-friendly thing
turt2live Dec 21, 2022
2e899ef
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
t3chguy Jul 28, 2023
75bf644
Update tests
t3chguy Jul 28, 2023
183a80a
Add legacy server toast
t3chguy Jul 28, 2023
ac6cec9
i18n
t3chguy Jul 28, 2023
9276bfa
Iterate
t3chguy Jul 28, 2023
c3f39bd
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
t3chguy Aug 1, 2023
69af23c
Fix tests
t3chguy Aug 1, 2023
2c8adbd
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
t3chguy Aug 8, 2023
238b30a
Remove doesServerSupportSeparateAddAndBind as we now require v1.1
t3chguy Aug 8, 2023
a9189a4
Add test
t3chguy Aug 8, 2023
030c604
Merge branch 'develop' into travis/mx-v1.1/api
t3chguy Aug 9, 2023
10c1fa8
Merge branch 'develop' into travis/mx-v1.1/api
t3chguy Aug 10, 2023
067c737
Fix general settings tab
t3chguy Aug 10, 2023
c9db1e5
Merge remote-tracking branch 'origin/travis/mx-v1.1/api' into travis/…
t3chguy Aug 10, 2023
5111690
Improve coverage
t3chguy Aug 10, 2023
8ec0059
Stop using r0 API endpoints
t3chguy Aug 10, 2023
adbd0f6
Remove more <v1.1 support
t3chguy Aug 10, 2023
8c47f83
iterate
t3chguy Aug 10, 2023
91738a4
i18n
t3chguy Aug 10, 2023
98ec8a4
Improve coverage
t3chguy Aug 10, 2023
4b5b041
Delint
t3chguy Aug 10, 2023
3afe577
Improve test coverage
t3chguy Aug 10, 2023
6ddec82
Improve coverage
t3chguy Aug 10, 2023
3535656
Fix test fixture
t3chguy Aug 10, 2023
4ed6f7e
Iterate
t3chguy Aug 10, 2023
7d75543
Fix object mangling
t3chguy Aug 10, 2023
af6a688
Merge branch 'develop' into travis/mx-v1.1/api
t3chguy Aug 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cypress/e2e/read-receipts/read-receipts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ describe("Read receipts", () => {
cy.intercept({
method: "POST",
url: new RegExp(
`http://localhost:\\d+/_matrix/client/r0/rooms/${uriEncodedOtherRoomId}/receipt/m\\.read/.+`,
`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/receipt/m\\.read/.+`,
),
}).as("receiptRequest");

Expand Down Expand Up @@ -321,7 +321,7 @@ describe("Read receipts", () => {

cy.intercept({
method: "POST",
url: new RegExp(`http://localhost:\\d+/_matrix/client/r0/rooms/${uriEncodedOtherRoomId}/read_markers`),
url: new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/read_markers`),
}).as("readMarkersRequest");

cy.findByRole("button", { name: "Jump to first unread message." }).click();
Expand All @@ -341,7 +341,7 @@ describe("Read receipts", () => {

cy.intercept({
method: "POST",
url: new RegExp(`http://localhost:\\d+/_matrix/client/r0/rooms/${uriEncodedOtherRoomId}/read_markers`),
url: new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/read_markers`),
}).as("readMarkersRequest");

cy.findByRole("button", { name: "Scroll to most recent messages" }).click();
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/timeline/timeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,14 +704,14 @@ describe("Timeline", () => {
});

it("should render url previews", () => {
cy.intercept("**/_matrix/media/r0/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*", {
cy.intercept("**/_matrix/media/v3/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*", {
statusCode: 200,
fixture: "riot.png",
headers: {
"Content-Type": "image/png",
},
}).as("mxc");
cy.intercept("**/_matrix/media/r0/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*", {
cy.intercept("**/_matrix/media/v3/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*", {
statusCode: 200,
body: {
"og:title": "Element Call",
Expand Down
2 changes: 1 addition & 1 deletion cypress/support/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ declare global {
Cypress.Commands.add(
"loginUser",
(homeserver: HomeserverInstance, username: string, password: string): Chainable<UserCredentials> => {
const url = `${homeserver.baseUrl}/_matrix/client/r0/login`;
const url = `${homeserver.baseUrl}/_matrix/client/v3/login`;
return cy
.request<{
access_token: string;
Expand Down
297 changes: 131 additions & 166 deletions src/AddThreepid.ts

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { decryptAES, encryptAES, IEncryptedPayload } from "matrix-js-sdk/src/cry
import { QueryDict } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { SSOAction } from "matrix-js-sdk/src/@types/auth";
import { MINIMUM_MATRIX_VERSION } from "matrix-js-sdk/src/version-support";

import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg";
import SecurityCustomisations from "./customisations/Security";
Expand Down Expand Up @@ -66,6 +67,7 @@ import { SdkContextClass } from "./contexts/SDKContext";
import { messageForLoginError } from "./utils/ErrorUtils";
import { completeOidcLogin } from "./utils/oidc/authorize";
import { persistOidcAuthenticatedSettings } from "./utils/oidc/persistOidcSettings";
import GenericToast from "./components/views/toasts/GenericToast";

const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
Expand Down Expand Up @@ -584,13 +586,43 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
},
false,
);
checkServerVersions();
return true;
} else {
logger.log("No previous session found.");
return false;
}
}

async function checkServerVersions(): Promise<void> {
MatrixClientPeg.get()
?.getVersions()
.then((response) => {
if (!response.versions.includes(MINIMUM_MATRIX_VERSION)) {
const toastKey = "LEGACY_SERVER";
ToastStore.sharedInstance().addOrReplaceToast({
key: toastKey,
title: _t("Your server is unsupported"),
props: {
description: _t(
"This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s without errors.",
{
version: MINIMUM_MATRIX_VERSION,
brand: SdkConfig.get().brand,
},
),
acceptLabel: _t("OK"),
onAccept: () => {
ToastStore.sharedInstance().dismissToast(toastKey);
},
},
component: GenericToast,
priority: 98,
});
}
});
}

async function handleLoadSessionFailure(e: unknown): Promise<boolean> {
logger.error("Unable to load session", e);

Expand Down
55 changes: 12 additions & 43 deletions src/components/structures/auth/ForgotPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ limitations under the License.

import React, { ReactNode } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { createClient } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";

import { _t, _td } from "../../../languageHandler";
Expand Down Expand Up @@ -81,7 +80,6 @@ interface State {
serverIsAlive: boolean;
serverDeadError: string;

serverSupportsControlOfDevicesLogout: boolean;
logoutDevices: boolean;
}

Expand All @@ -104,26 +102,18 @@ export default class ForgotPassword extends React.Component<Props, State> {
// be seeing.
serverIsAlive: true,
serverDeadError: "",
serverSupportsControlOfDevicesLogout: false,
logoutDevices: false,
};
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
}

public componentDidMount(): void {
this.checkServerCapabilities(this.props.serverConfig);
}

public componentDidUpdate(prevProps: Readonly<Props>): void {
if (
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
) {
// Do a liveliness check on the new URLs
this.checkServerLiveliness(this.props.serverConfig);

// Do capabilities check on new URLs
this.checkServerCapabilities(this.props.serverConfig);
}
}

Expand All @@ -146,19 +136,6 @@ export default class ForgotPassword extends React.Component<Props, State> {
}
}

private async checkServerCapabilities(serverConfig: ValidatedServerConfig): Promise<void> {
const tempClient = createClient({
baseUrl: serverConfig.hsUrl,
});

const serverSupportsControlOfDevicesLogout = await tempClient.doesServerSupportLogoutDevices();

this.setState({
logoutDevices: !serverSupportsControlOfDevicesLogout,
serverSupportsControlOfDevicesLogout,
});
}

private async onPhaseEmailInputSubmit(): Promise<void> {
this.phase = Phase.SendingEmail;

Expand Down Expand Up @@ -376,16 +353,10 @@ export default class ForgotPassword extends React.Component<Props, State> {
description: (
<div>
<p>
{!this.state.serverSupportsControlOfDevicesLogout
? _t(
"Resetting your password on this homeserver will cause all of your devices to be " +
"signed out. This will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
)
: _t(
"Signing out your devices will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
)}
{_t(
"Signing out your devices will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
)}
</p>
<p>
{_t(
Expand Down Expand Up @@ -446,16 +417,14 @@ export default class ForgotPassword extends React.Component<Props, State> {
autoComplete="new-password"
/>
</div>
{this.state.serverSupportsControlOfDevicesLogout ? (
<div className="mx_AuthBody_fieldRow">
<StyledCheckbox
onChange={() => this.setState({ logoutDevices: !this.state.logoutDevices })}
checked={this.state.logoutDevices}
>
{_t("Sign out of all devices")}
</StyledCheckbox>
</div>
) : null}
<div className="mx_AuthBody_fieldRow">
<StyledCheckbox
onChange={() => this.setState({ logoutDevices: !this.state.logoutDevices })}
checked={this.state.logoutDevices}
>
{_t("Sign out of all devices")}
</StyledCheckbox>
</div>
{this.state.errorText && <ErrorMessage message={this.state.errorText} />}
<button type="submit" className="mx_Login_submit">
{submitButtonChild}
Expand Down
81 changes: 5 additions & 76 deletions src/components/views/settings/ChangePassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ limitations under the License.
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";

import type ExportE2eKeysDialog from "../../../async-components/views/dialogs/security/ExportE2eKeysDialog";
import Field from "../elements/Field";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import AccessibleButton from "../elements/AccessibleButton";
Expand All @@ -29,7 +28,6 @@ import Modal from "../../../Modal";
import PassphraseField from "../auth/PassphraseField";
import { PASSWORD_MIN_SCORE } from "../auth/RegistrationForm";
import SetEmailDialog from "../dialogs/SetEmailDialog";
import QuestionDialog from "../dialogs/QuestionDialog";

const FIELD_OLD_PASSWORD = "field_old_password";
const FIELD_NEW_PASSWORD = "field_new_password";
Expand All @@ -43,11 +41,7 @@ enum Phase {
}

interface IProps {
onFinished: (outcome: {
didSetEmail?: boolean;
/** Was one or more other devices logged out whilst changing the password */
didLogoutOutOtherDevices: boolean;
}) => void;
onFinished: (outcome: { didSetEmail?: boolean }) => void;
onError: (error: Error) => void;
rowClassName?: string;
buttonClassName?: string;
Expand Down Expand Up @@ -95,58 +89,10 @@ export default class ChangePassword extends React.Component<IProps, IState> {
private async onChangePassword(oldPassword: string, newPassword: string): Promise<void> {
const cli = MatrixClientPeg.safeGet();

// if the server supports it then don't sign user out of all devices
const serverSupportsControlOfDevicesLogout = await cli.doesServerSupportLogoutDevices();
const userHasOtherDevices = (await cli.getDevices()).devices.length > 1;

if (userHasOtherDevices && !serverSupportsControlOfDevicesLogout && this.props.confirm) {
// warn about logging out all devices
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Warning!"),
description: (
<div>
<p>
{_t(
"Changing your password on this homeserver will cause all of your other devices to be " +
"signed out. This will delete the message encryption keys stored on them, and may make " +
"encrypted chat history unreadable.",
)}
</p>
<p>
{_t(
"If you want to retain access to your chat history in encrypted rooms you should first " +
"export your room keys and re-import them afterwards.",
)}
</p>
<p>
{_t(
"You can also ask your homeserver admin to upgrade the server to change this behaviour.",
)}
</p>
</div>
),
button: _t("Continue"),
extraButtons: [
<button key="exportRoomKeys" className="mx_Dialog_primary" onClick={this.onExportE2eKeysClicked}>
{_t("Export E2E room keys")}
</button>,
],
});

const [confirmed] = await finished;
if (!confirmed) return;
}

this.changePassword(cli, oldPassword, newPassword, serverSupportsControlOfDevicesLogout, userHasOtherDevices);
this.changePassword(cli, oldPassword, newPassword);
}

private changePassword(
cli: MatrixClient,
oldPassword: string,
newPassword: string,
serverSupportsControlOfDevicesLogout: boolean,
userHasOtherDevices: boolean,
): void {
private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void {
const authDict = {
type: "m.login.password",
identifier: {
Expand All @@ -163,23 +109,17 @@ export default class ChangePassword extends React.Component<IProps, IState> {
phase: Phase.Uploading,
});

const logoutDevices = serverSupportsControlOfDevicesLogout ? false : undefined;

// undefined or true mean all devices signed out
const didLogoutOutOtherDevices = !serverSupportsControlOfDevicesLogout && userHasOtherDevices;

cli.setPassword(authDict, newPassword, logoutDevices)
cli.setPassword(authDict, newPassword, false)
.then(
() => {
if (this.props.shouldAskForEmail) {
return this.optionallySetEmail().then((confirmed) => {
this.props.onFinished({
didSetEmail: confirmed,
didLogoutOutOtherDevices,
});
});
} else {
this.props.onFinished({ didLogoutOutOtherDevices });
this.props.onFinished({});
}
},
(err) => {
Expand Down Expand Up @@ -229,17 +169,6 @@ export default class ChangePassword extends React.Component<IProps, IState> {
return modal.finished.then(([confirmed]) => !!confirmed);
}

private onExportE2eKeysClicked = (): void => {
Modal.createDialogAsync(
import("../../../async-components/views/dialogs/security/ExportE2eKeysDialog") as unknown as Promise<
typeof ExportE2eKeysDialog
>,
{
matrixClient: MatrixClientPeg.safeGet(),
},
);
};

private markFieldValid(fieldID: FieldType, valid?: boolean): void {
const { fieldValid } = this.state;
fieldValid[fieldID] = valid;
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/settings/account/PhoneNumbers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
?.haveMsisdnToken(token)
.then(([finished] = []) => {
let newPhoneNumber = this.state.newPhoneNumber;
if (finished) {
if (finished !== false) {
const msisdns = [...this.props.msisdns, { address, medium: ThreepidMedium.Phone }];
this.props.onMsisdnsChange(msisdns);
newPhoneNumber = "";
Expand Down
Loading
Loading