Support Matrix 1.1 (drop legacy r0 versions) (#9819)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
f9e79fd5d6
commit
180fcaa70f
32 changed files with 712 additions and 440 deletions
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue