Support Matrix 1.1 (drop legacy r0 versions) (#9819)

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Travis Ralston 2023-08-14 02:25:13 -06:00 committed by GitHub
parent f9e79fd5d6
commit 180fcaa70f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 712 additions and 440 deletions

View file

@ -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";
@ -81,7 +80,6 @@ interface State {
serverIsAlive: boolean;
serverDeadError: string;
serverSupportsControlOfDevicesLogout: boolean;
logoutDevices: boolean;
}
@ -104,16 +102,11 @@ 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 ||
@ -121,9 +114,6 @@ export default class ForgotPassword extends React.Component<Props, State> {
) {
// Do a liveliness check on the new URLs
this.checkServerLiveliness(this.props.serverConfig);
// Do capabilities check on new URLs
this.checkServerCapabilities(this.props.serverConfig);
}
}
@ -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;
@ -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(
@ -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}

View file

@ -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";
@ -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";
@ -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;
@ -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: {
@ -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) => {
@ -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;

View file

@ -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 = "";

View file

@ -77,10 +77,6 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
}
private async changeBinding({ bind, label, errorTitle }: Binding): Promise<void> {
if (!(await MatrixClientPeg.safeGet().doesServerSupportSeparateAddAndBind())) {
return this.changeBindingTangledAddBind({ bind, label, errorTitle });
}
const { medium, address } = this.props.email;
try {
@ -113,41 +109,6 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
}
}
private async changeBindingTangledAddBind({ bind, label, errorTitle }: Binding): Promise<void> {
const { medium, address } = this.props.email;
const task = new AddThreepid(MatrixClientPeg.safeGet());
this.setState({
verifying: true,
continueDisabled: true,
addTask: task,
});
try {
await MatrixClientPeg.safeGet().deleteThreePid(medium, address);
if (bind) {
await task.bindEmailAddress(address);
} else {
await task.addEmailAddress(address);
}
this.setState({
continueDisabled: false,
bound: bind,
});
} catch (err) {
logger.error(`changeBindingTangledAddBind: Unable to ${label} email address ${address}`, err);
this.setState({
verifying: false,
continueDisabled: false,
addTask: null,
});
Modal.createDialog(ErrorDialog, {
title: errorTitle,
description: extractErrorMessageFromError(err, _t("Operation failed")),
});
}
}
private onRevokeClick = (e: ButtonEvent): void => {
e.stopPropagation();
e.preventDefault();

View file

@ -73,10 +73,6 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
}
private async changeBinding({ bind, label, errorTitle }: Binding): Promise<void> {
if (!(await MatrixClientPeg.safeGet().doesServerSupportSeparateAddAndBind())) {
return this.changeBindingTangledAddBind({ bind, label, errorTitle });
}
const { medium, address } = this.props.msisdn;
try {
@ -114,47 +110,6 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
}
}
private async changeBindingTangledAddBind({ bind, label, errorTitle }: Binding): Promise<void> {
const { medium, address } = this.props.msisdn;
const task = new AddThreepid(MatrixClientPeg.safeGet());
this.setState({
verifying: true,
continueDisabled: true,
addTask: task,
});
try {
await MatrixClientPeg.safeGet().deleteThreePid(medium, address);
// XXX: Sydent will accept a number without country code if you add
// a leading plus sign to a number in E.164 format (which the 3PID
// address is), but this goes against the spec.
// See https://github.com/matrix-org/matrix-doc/issues/2222
if (bind) {
// @ts-ignore
await task.bindMsisdn(null, `+${address}`);
} else {
// @ts-ignore
await task.addMsisdn(null, `+${address}`);
}
this.setState({
continueDisabled: false,
bound: bind,
});
} catch (err) {
logger.error(`changeBindingTangledAddBind: Unable to ${label} phone number ${address}`, err);
this.setState({
verifying: false,
continueDisabled: false,
addTask: null,
});
Modal.createDialog(ErrorDialog, {
title: errorTitle,
description: extractErrorMessageFromError(err, _t("Operation failed")),
});
}
}
private onRevokeClick = (e: ButtonEvent): void => {
e.stopPropagation();
e.preventDefault();

View file

@ -37,7 +37,6 @@ import { Service, ServicePolicyPair, startTermsFlow } from "../../../../../Terms
import IdentityAuthClient from "../../../../../IdentityAuthClient";
import { abbreviateUrl } from "../../../../../utils/UrlUtils";
import { getThreepidsWithBindStatus } from "../../../../../boundThreepids";
import Spinner from "../../../elements/Spinner";
import { SettingLevel } from "../../../../../settings/SettingLevel";
import { UIFeature } from "../../../../../settings/UIFeature";
import { ActionPayload } from "../../../../../dispatcher/payloads";
@ -70,7 +69,6 @@ interface IState {
spellCheckEnabled?: boolean;
spellCheckLanguages: string[];
haveIdServer: boolean;
serverSupportsSeparateAddAndBind?: boolean;
idServerHasUnsignedTerms: boolean;
requiredPolicyInfo:
| {
@ -166,8 +164,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private async getCapabilities(): Promise<void> {
const cli = this.context;
const serverSupportsSeparateAddAndBind = await cli.doesServerSupportSeparateAddAndBind();
const capabilities = await cli.getCapabilities(); // this is cached
const changePasswordCap = capabilities["m.change_password"];
@ -179,7 +175,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
const delegatedAuthConfig = M_AUTHENTICATION.findIn<IDelegatedAuthConfig | undefined>(cli.getClientWellKnown());
const externalAccountManagementUrl = delegatedAuthConfig?.account;
this.setState({ serverSupportsSeparateAddAndBind, canChangePassword, externalAccountManagementUrl });
this.setState({ canChangePassword, externalAccountManagementUrl });
}
private async getThreepidState(): Promise<void> {
@ -303,12 +299,8 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
});
};
private onPasswordChanged = ({ didLogoutOutOtherDevices }: { didLogoutOutOtherDevices: boolean }): void => {
let description = _t("Your password was successfully changed.");
if (didLogoutOutOtherDevices) {
description +=
" " + _t("You will not receive push notifications on other devices until you sign back in to them.");
}
private onPasswordChanged = (): void => {
const description = _t("Your password was successfully changed.");
// TODO: Figure out a design that doesn't involve replacing the current dialog
Modal.createDialog(ErrorDialog, {
title: _t("Success"),
@ -327,15 +319,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private renderAccountSection(): JSX.Element {
let threepidSection: ReactNode = null;
// For older homeservers without separate 3PID add and bind methods (MSC2290),
// we use a combo add with bind option API which requires an identity server to
// validate 3PID ownership even if we're just adding to the homeserver only.
// For newer homeservers with separate 3PID add and bind methods (MSC2290),
// there is no such concern, so we can always show the HS account 3PIDs.
if (
SettingsStore.getValue(UIFeature.ThirdPartyID) &&
(this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true)
) {
if (SettingsStore.getValue(UIFeature.ThirdPartyID)) {
const emails = this.state.loading3pids ? (
<InlineSpinner />
) : (
@ -365,8 +349,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
</SettingsSubsection>
</>
);
} else if (this.state.serverSupportsSeparateAddAndBind === null) {
threepidSection = <Spinner />;
}
let passwordChangeSection: ReactNode = null;