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

@ -41,7 +41,11 @@ enum Phase {
}
interface IProps {
onFinished?: ({ didSetEmail: boolean }?) => void;
onFinished?: (outcome: {
didSetEmail?: boolean;
/** Was one or more other devices logged out whilst changing the password */
didLogoutOutOtherDevices: boolean;
}) => void;
onError?: (error: {error: string}) => void;
rowClassName?: string;
buttonClassName?: string;
@ -82,48 +86,58 @@ export default class ChangePassword extends React.Component<IProps, IState> {
};
}
private onChangePassword(oldPassword: string, newPassword: string): void {
private async onChangePassword(oldPassword: string, newPassword: string): Promise<void> {
const cli = MatrixClientPeg.get();
if (!this.props.confirm) {
this.changePassword(cli, oldPassword, newPassword);
return;
// 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.createTrackedDialog<[boolean]>('Change Password', '', 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;
}
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, serverSupportsControlOfDevicesLogout, userHasOtherDevices);
}
private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void {
private changePassword(
cli: MatrixClient,
oldPassword: string,
newPassword: string,
serverSupportsControlOfDevicesLogout: boolean,
userHasOtherDevices: boolean,
): void {
const authDict = {
type: 'm.login.password',
identifier: {
@ -140,15 +154,21 @@ export default class ChangePassword extends React.Component<IProps, IState> {
phase: Phase.Uploading,
});
cli.setPassword(authDict, newPassword).then(() => {
const logoutDevices = serverSupportsControlOfDevicesLogout ? false : undefined;
// undefined or true mean all devices signed out
const didLogoutOutOtherDevices = !serverSupportsControlOfDevicesLogout && userHasOtherDevices;
cli.setPassword(authDict, newPassword, logoutDevices).then(() => {
if (this.props.shouldAskForEmail) {
return this.optionallySetEmail().then((confirmed) => {
this.props.onFinished({
didSetEmail: confirmed,
didLogoutOutOtherDevices,
});
});
} else {
this.props.onFinished();
this.props.onFinished({ didLogoutOutOtherDevices });
}
}, (err) => {
this.props.onError(err);
@ -279,7 +299,7 @@ export default class ChangePassword extends React.Component<IProps, IState> {
if (err) {
this.props.onError(err);
} else {
this.onChangePassword(oldPassword, newPassword);
return this.onChangePassword(oldPassword, newPassword);
}
};

View file

@ -260,14 +260,17 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
});
};
private onPasswordChanged = (): void => {
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.",
);
}
// TODO: Figure out a design that doesn't involve replacing the current dialog
Modal.createTrackedDialog('Password changed', '', ErrorDialog, {
title: _t("Success"),
description: _t(
"Your password was successfully changed. You will not receive " +
"push notifications on other sessions until you log back in to them",
) + ".",
description,
});
};