Allow user to control if they are signed out of all devices when changing password (#8259)

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Hugh Nimmo-Smith 2022-04-22 18:15:38 +01:00 committed by GitHub
parent ee2ee3c08c
commit bb4064ff43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 157 additions and 76 deletions

View file

@ -19,6 +19,7 @@ limitations under the License.
import React from 'react';
import classNames from 'classnames';
import { logger } from "matrix-js-sdk/src/logger";
import { createClient } from "matrix-js-sdk/src/matrix";
import { _t, _td } from '../../../languageHandler';
import Modal from "../../../Modal";
@ -37,6 +38,7 @@ import AuthHeader from "../../views/auth/AuthHeader";
import AuthBody from "../../views/auth/AuthBody";
import PassphraseConfirmField from "../../views/auth/PassphraseConfirmField";
import AccessibleButton from '../../views/elements/AccessibleButton';
import StyledCheckbox from '../../views/elements/StyledCheckbox';
enum Phase {
// Show the forgot password inputs
@ -72,6 +74,9 @@ interface IState {
serverDeadError: string;
currentHttpRequest?: Promise<any>;
serverSupportsControlOfDevicesLogout: boolean;
logoutDevices: boolean;
}
enum ForgotPasswordField {
@ -97,11 +102,14 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
serverIsAlive: true,
serverErrorIsFatal: false,
serverDeadError: "",
serverSupportsControlOfDevicesLogout: false,
logoutDevices: false,
};
public componentDidMount() {
this.reset = null;
this.checkServerLiveliness(this.props.serverConfig);
this.checkServerCapabilities(this.props.serverConfig);
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
@ -112,6 +120,9 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
// Do a liveliness check on the new URLs
this.checkServerLiveliness(newProps.serverConfig);
// Do capabilities check on new URLs
this.checkServerCapabilities(newProps.serverConfig);
}
private async checkServerLiveliness(serverConfig): Promise<void> {
@ -129,12 +140,25 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
}
}
public submitPasswordReset(email: string, password: string): void {
private async checkServerCapabilities(serverConfig: ValidatedServerConfig): Promise<void> {
const tempClient = createClient({
baseUrl: serverConfig.hsUrl,
});
const serverSupportsControlOfDevicesLogout = await tempClient.doesServerSupportLogoutDevices();
this.setState({
logoutDevices: !serverSupportsControlOfDevicesLogout,
serverSupportsControlOfDevicesLogout,
});
}
public submitPasswordReset(email: string, password: string, logoutDevices = true): void {
this.setState({
phase: Phase.SendingEmail,
});
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
this.reset.resetPassword(email, password).then(() => {
this.reset.resetPassword(email, password, logoutDevices).then(() => {
this.setState({
phase: Phase.EmailSent,
});
@ -174,24 +198,35 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
return;
}
Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, {
title: _t('Warning!'),
description:
<div>
{ _t(
"Changing your password will reset any end-to-end encryption keys " +
"on all of your sessions, making encrypted chat history unreadable. Set up " +
"Key Backup or export your room keys from another session before resetting your " +
"password.",
) }
</div>,
button: _t('Continue'),
onFinished: (confirmed) => {
if (confirmed) {
this.submitPasswordReset(this.state.email, this.state.password);
}
},
});
if (this.state.logoutDevices) {
const { finished } = Modal.createTrackedDialog<[boolean]>('Forgot Password Warning', '', QuestionDialog, {
title: _t('Warning!'),
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.",
)
}</p>
<p>{ _t(
"If you want to retain access to your chat history in encrypted rooms, set up Key Backup " +
"or export your message keys from one of your other devices before proceeding.",
) }</p>
</div>,
button: _t('Continue'),
});
const [confirmed] = await finished;
if (!confirmed) return;
}
this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices);
};
private async verifyFieldsBeforeSubmit() {
@ -316,6 +351,13 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
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 all devices") }
</StyledCheckbox>
</div> : null
}
<span>{ _t(
'A verification email will be sent to your inbox to confirm ' +
'setting your new password.',
@ -355,11 +397,14 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
renderDone() {
return <div>
<p>{ _t("Your password has been reset.") }</p>
<p>{ _t(
"You have been logged out of all sessions and will no longer receive " +
"push notifications. To re-enable notifications, sign in again on each " +
"device.",
) }</p>
{ this.state.logoutDevices ?
<p>{ _t(
"You have been logged out of all devices and will no longer receive " +
"push notifications. To re-enable notifications, sign in again on each " +
"device.",
) }</p>
: null
}
<input
className="mx_Login_submit"
type="button"