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

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

Merged
merged 24 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9825e44
Allow user to control if they are signed out of all sessions when res…
hughns Apr 8, 2022
ac4b135
Don't sign out all devices when changing password from Settings tab
hughns Apr 8, 2022
e8eed9d
Use camelcase variable name
hughns Apr 8, 2022
49b12b0
Add argument type
hughns Apr 8, 2022
564f0fd
session => device
hughns Apr 8, 2022
5bebf72
Wording changes based on feedback in PR
hughns Apr 8, 2022
f360a64
Merge branch 'hughns/logout-devices-control' of https://github.com/ma…
hughns Apr 8, 2022
13e95bf
Merge branch 'develop' into hughns/logout-devices-control
hughns Apr 13, 2022
68f9551
UI to respect if server has capability to control device logout
hughns Apr 13, 2022
981813d
Revert "Don't sign out all devices when changing password from Settin…
hughns Apr 13, 2022
e6415b2
If homeserver supports it then don't sign out all devices when changi…
hughns Apr 13, 2022
5c48520
Wording revisions
hughns Apr 13, 2022
fd36f66
Whitespace fix
hughns Apr 13, 2022
c4380c9
Remove trailing whitespace from translations
hughns Apr 13, 2022
e8626ed
Wording and whitespace
hughns Apr 13, 2022
bd3a12c
Add proper capability check based on support spec version
hughns Apr 13, 2022
27a7bf1
Merge branch 'develop' into hughns/logout-devices-control
hughns Apr 13, 2022
36c6fde
Update src/components/views/settings/ChangePassword.tsx
hughns Apr 13, 2022
20b3316
Update src/components/structures/auth/ForgotPassword.tsx
hughns Apr 13, 2022
cff80ac
Refactor to use Modal promises
hughns Apr 13, 2022
fe384cd
Take account of whether devices where signed out in password change c…
hughns Apr 19, 2022
f3260a0
Update translations
hughns Apr 19, 2022
853e0e9
Merge branch 'develop' into hughns/logout-devices-control
hughns Apr 21, 2022
9038ea7
Only warn user when changing password in Settings if they have other …
hughns Apr 21, 2022
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
11 changes: 9 additions & 2 deletions src/PasswordReset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default class PasswordReset {
private clientSecret: string;
private password: string;
private sessionId: string;
private logoutDevices: boolean;

/**
* Configure the endpoints for password resetting.
Expand All @@ -50,10 +51,16 @@ export default class PasswordReset {
* sending an email to the provided email address.
* @param {string} emailAddress The email address
* @param {string} newPassword The new password for the account.
* @param {boolean} logoutDevices Should all devices be signed out after the reset? Defaults to `true`.
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
*/
public resetPassword(emailAddress: string, newPassword: string): Promise<IRequestTokenResponse> {
public resetPassword(
emailAddress: string,
newPassword: string,
logoutDevices = true,
): Promise<IRequestTokenResponse> {
this.password = newPassword;
this.logoutDevices = logoutDevices;
return this.client.requestPasswordEmailToken(emailAddress, this.clientSecret, 1).then((res) => {
this.sessionId = res.sid;
return res;
Expand Down Expand Up @@ -90,7 +97,7 @@ export default class PasswordReset {
// See https://github.com/matrix-org/matrix-doc/issues/2220
threepid_creds: creds,
threepidCreds: creds,
}, this.password);
}, this.password, this.logoutDevices);
} catch (err) {
if (err.httpStatus === 401) {
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
Expand Down
66 changes: 41 additions & 25 deletions src/components/structures/auth/ForgotPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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
Expand Down Expand Up @@ -72,6 +73,8 @@ interface IState {
serverDeadError: string;

currentHttpRequest?: Promise<any>;

logout_devices: boolean;
hughns marked this conversation as resolved.
Show resolved Hide resolved
}

enum ForgotPasswordField {
Expand All @@ -97,6 +100,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
serverIsAlive: true,
serverErrorIsFatal: false,
serverDeadError: "",
logout_devices: false,
};

public componentDidMount() {
Expand Down Expand Up @@ -129,12 +133,12 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
}
}

public submitPasswordReset(email: string, password: string): void {
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,
});
Expand Down Expand Up @@ -174,24 +178,28 @@ 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.logout_devices) {
Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, {
title: _t('Warning!'),
description:
<div>
{ _t(
"Signing out all sessions will reset the end-to-end encryption keys " +
"making encrypted chat history unreadable. Set up " +
"Key Backup or export your room keys from another session before resetting your " +
"password.",
hughns marked this conversation as resolved.
Show resolved Hide resolved
) }
</div>,
button: _t('Continue'),
onFinished: (confirmed) => {
hughns marked this conversation as resolved.
Show resolved Hide resolved
if (confirmed) {
this.submitPasswordReset(this.state.email, this.state.password, this.state.logout_devices);
}
},
});
} else {
this.submitPasswordReset(this.state.email, this.state.password, this.state.logout_devices);
}
};

private async verifyFieldsBeforeSubmit() {
Expand Down Expand Up @@ -314,6 +322,11 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
autoComplete="new-password"
/>
</div>
<div className="mx_AuthBody_fieldRow">
<StyledCheckbox onChange={() => this.setState({ logout_devices: !this.state.logout_devices })} checked={this.state.logout_devices}>
{ _t("Sign out all sessions") }
</StyledCheckbox>
</div>
<span>{ _t(
'A verification email will be sent to your inbox to confirm ' +
'setting your new password.',
Expand Down Expand Up @@ -353,11 +366,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.logout_devices ?
<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>
: null
}
<input
className="mx_Login_submit"
type="button"
Expand Down
57 changes: 4 additions & 53 deletions src/components/views/settings/ChangePassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { ComponentType } from 'react';
import React from 'react';
import { MatrixClient } from "matrix-js-sdk/src/client";

import Field from "../elements/Field";
Expand All @@ -28,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 @@ -47,7 +46,6 @@ interface IProps {
buttonClassName?: string;
buttonKind?: string;
buttonLabel?: string;
confirm?: boolean;
// Whether to autoFocus the new password input
autoFocusNewPasswordInput?: boolean;
className?: string;
Expand All @@ -66,8 +64,6 @@ export default class ChangePassword extends React.Component<IProps, IState> {
public static defaultProps: Partial<IProps> = {
onFinished() {},
onError() {},

confirm: true,
};

constructor(props: IProps) {
Expand All @@ -85,42 +81,7 @@ export default class ChangePassword extends React.Component<IProps, IState> {
private onChangePassword(oldPassword: string, newPassword: string): void {
const cli = MatrixClientPeg.get();

if (!this.props.confirm) {
this.changePassword(cli, oldPassword, newPassword);
return;
}

Modal.createTrackedDialog('Change Password', '', QuestionDialog, {
title: _t("Warning!"),
description:
<div>
{ _t(
'Changing password will currently reset any end-to-end encryption keys on all sessions, ' +
'making encrypted chat history unreadable, unless you first export your room keys ' +
'and re-import them afterwards. ' +
'In future this will be improved.',
) }
{ ' ' }
<a href="https://github.com/vector-im/element-web/issues/2671" target="_blank" rel="noreferrer noopener">
https://github.com/vector-im/element-web/issues/2671
</a>
</div>,
button: _t("Continue"),
extraButtons: [
<button
key="exportRoomKeys"
className="mx_Dialog_primary"
onClick={this.onExportE2eKeysClicked}
>
{ _t('Export E2E room keys') }
</button>,
],
onFinished: (confirmed) => {
if (confirmed) {
this.changePassword(cli, oldPassword, newPassword);
}
},
});
this.changePassword(cli, oldPassword, newPassword);
}

private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void {
Expand All @@ -140,7 +101,8 @@ export default class ChangePassword extends React.Component<IProps, IState> {
phase: Phase.Uploading,
});

cli.setPassword(authDict, newPassword).then(() => {
// This no longer logs out all sessions:
cli.setPassword(authDict, newPassword, false).then(() => {
hughns marked this conversation as resolved.
Show resolved Hide resolved
if (this.props.shouldAskForEmail) {
return this.optionallySetEmail().then((confirmed) => {
this.props.onFinished({
Expand Down Expand Up @@ -182,17 +144,6 @@ export default class ChangePassword extends React.Component<IProps, IState> {
return modal.finished.then(([confirmed]) => confirmed);
}

private onExportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
import(
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
) as unknown as Promise<ComponentType<{}>>,
{
matrixClient: MatrixClientPeg.get(),
},
);
};

private markFieldValid(fieldID: string, valid: boolean): void {
const { fieldValid } = this.state;
fieldValid[fieldID] = valid;
Expand Down
8 changes: 4 additions & 4 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1185,9 +1185,6 @@
"Failed to upload profile picture!": "Failed to upload profile picture!",
"Upload new:": "Upload new:",
"No display name": "No display name",
"Warning!": "Warning!",
"Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"Export E2E room keys": "Export E2E room keys",
"New passwords don't match": "New passwords don't match",
"Passwords can't be empty": "Passwords can't be empty",
"Do you want to set an email address?": "Do you want to set an email address?",
Expand Down Expand Up @@ -1216,6 +1213,7 @@
"Homeserver feature support:": "Homeserver feature support:",
"exists": "exists",
"<not supported>": "<not supported>",
"Export E2E room keys": "Export E2E room keys",
"Import E2E room keys": "Import E2E room keys",
"Cryptography": "Cryptography",
"Session ID:": "Session ID:",
Expand Down Expand Up @@ -1998,6 +1996,7 @@
"Unmute": "Unmute",
"Mute": "Mute",
"Failed to change power level": "Failed to change power level",
"Warning!": "Warning!",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Are you sure?": "Are you sure?",
"Deactivate user?": "Deactivate user?",
Expand Down Expand Up @@ -3137,11 +3136,12 @@
"Really reset verification keys?": "Really reset verification keys?",
"Skip verification for now": "Skip verification for now",
"Failed to send email": "Failed to send email",
"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.": "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.",
"Signing out all sessions will reset the end-to-end encryption keys making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Signing out all sessions will reset the end-to-end encryption keys making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.",
hughns marked this conversation as resolved.
Show resolved Hide resolved
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
"The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.",
"A new password must be entered.": "A new password must be entered.",
"New passwords must match each other.": "New passwords must match each other.",
"Sign out all sessions": "Sign out all sessions",
"A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.",
"Send Reset Email": "Send Reset Email",
"Sign in instead": "Sign in instead",
Expand Down