From 9825e445763db2712660f2df7675031261370717 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 8 Apr 2022 10:54:01 +0100 Subject: [PATCH 01/20] Allow user to control if they are signed out of all sessions when resetting password --- src/PasswordReset.ts | 11 +++- .../structures/auth/ForgotPassword.tsx | 66 ++++++++++++------- src/i18n/strings/en_EN.json | 3 +- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/PasswordReset.ts b/src/PasswordReset.ts index f5d9ab77dcb..df812bafb2c 100644 --- a/src/PasswordReset.ts +++ b/src/PasswordReset.ts @@ -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. @@ -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 { + public resetPassword( + emailAddress: string, + newPassword: string, + logoutDevices = true, + ): Promise { this.password = newPassword; + this.logoutDevices = logoutDevices; return this.client.requestPasswordEmailToken(emailAddress, this.clientSecret, 1).then((res) => { this.sessionId = res.sid; return res; @@ -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'); diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 58b0073c443..1a2b6be4ab4 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -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 @@ -72,6 +73,8 @@ interface IState { serverDeadError: string; currentHttpRequest?: Promise; + + logout_devices: boolean; } enum ForgotPasswordField { @@ -97,6 +100,7 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", + logout_devices: false, }; public componentDidMount() { @@ -129,12 +133,12 @@ export default class ForgotPassword extends React.Component { } } - 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, }); @@ -174,24 +178,28 @@ export default class ForgotPassword extends React.Component { return; } - Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, { - title: _t('Warning!'), - description: -
- { _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.", - ) } -
, - 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: +
+ { _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.", + ) } +
, + button: _t('Continue'), + onFinished: (confirmed) => { + 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() { @@ -314,6 +322,11 @@ export default class ForgotPassword extends React.Component { autoComplete="new-password" /> +
+ this.setState({ logout_devices: !this.state.logout_devices })} checked={this.state.logout_devices}> + { _t("Sign out all sessions") } + +
{ _t( 'A verification email will be sent to your inbox to confirm ' + 'setting your new password.', @@ -353,11 +366,14 @@ export default class ForgotPassword extends React.Component { renderDone() { return

{ _t("Your password has been reset.") }

-

{ _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.", - ) }

+ { this.state.logout_devices ? +

{ _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.", + ) }

+ : null + } Date: Fri, 8 Apr 2022 10:55:48 +0100 Subject: [PATCH 02/20] Don't sign out all devices when changing password from Settings tab This removes the confirm= param on the ChangePassword component --- .../views/settings/ChangePassword.tsx | 57 ++----------------- src/i18n/strings/en_EN.json | 5 +- 2 files changed, 6 insertions(+), 56 deletions(-) diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index f736e5f6f51..d2b4f178d57 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -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"; @@ -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'; @@ -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; @@ -66,8 +64,6 @@ export default class ChangePassword extends React.Component { public static defaultProps: Partial = { onFinished() {}, onError() {}, - - confirm: true, }; constructor(props: IProps) { @@ -85,42 +81,7 @@ export default class ChangePassword extends React.Component { 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: -
- { _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.', - ) } - { ' ' } - - https://github.com/vector-im/element-web/issues/2671 - -
, - button: _t("Continue"), - extraButtons: [ - , - ], - onFinished: (confirmed) => { - if (confirmed) { - this.changePassword(cli, oldPassword, newPassword); - } - }, - }); + this.changePassword(cli, oldPassword, newPassword); } private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void { @@ -140,7 +101,8 @@ export default class ChangePassword extends React.Component { phase: Phase.Uploading, }); - cli.setPassword(authDict, newPassword).then(() => { + // This no longer logs out all sessions: + cli.setPassword(authDict, newPassword, false).then(() => { if (this.props.shouldAskForEmail) { return this.optionallySetEmail().then((confirmed) => { this.props.onFinished({ @@ -182,17 +144,6 @@ export default class ChangePassword extends React.Component { 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>, - { - matrixClient: MatrixClientPeg.get(), - }, - ); - }; - private markFieldValid(fieldID: string, valid: boolean): void { const { fieldValid } = this.state; fieldValid[fieldID] = valid; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d1297499eaa..9e17b563b61 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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?", @@ -1216,6 +1213,7 @@ "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", "": "", + "Export E2E room keys": "Export E2E room keys", "Import E2E room keys": "Import E2E room keys", "Cryptography": "Cryptography", "Session ID:": "Session ID:", @@ -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?", From e8eed9df2273149e0caf0eb9296c56cabac9f461 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 8 Apr 2022 16:34:46 +0100 Subject: [PATCH 03/20] Use camelcase variable name --- src/components/structures/auth/ForgotPassword.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 1a2b6be4ab4..cac4aef91ec 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -74,7 +74,7 @@ interface IState { currentHttpRequest?: Promise; - logout_devices: boolean; + logoutDevices: boolean; } enum ForgotPasswordField { @@ -100,7 +100,7 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", - logout_devices: false, + logoutDevices: false, }; public componentDidMount() { @@ -178,7 +178,7 @@ export default class ForgotPassword extends React.Component { return; } - if (this.state.logout_devices) { + if (this.state.logoutDevices) { Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, { title: _t('Warning!'), description: @@ -193,12 +193,12 @@ export default class ForgotPassword extends React.Component { button: _t('Continue'), onFinished: (confirmed) => { if (confirmed) { - this.submitPasswordReset(this.state.email, this.state.password, this.state.logout_devices); + this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices); } }, }); } else { - this.submitPasswordReset(this.state.email, this.state.password, this.state.logout_devices); + this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices); } }; @@ -323,7 +323,7 @@ export default class ForgotPassword extends React.Component { />
- this.setState({ logout_devices: !this.state.logout_devices })} checked={this.state.logout_devices}> + this.setState({ logoutDevices: !this.state.logoutDevices })} checked={this.state.logoutDevices}> { _t("Sign out all sessions") }
@@ -366,7 +366,7 @@ export default class ForgotPassword extends React.Component { renderDone() { return

{ _t("Your password has been reset.") }

- { this.state.logout_devices ? + { this.state.logoutDevices ?

{ _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 " + From 49b12b0ea9f32dae6ffe74b2afc93b0cf27ffec8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 8 Apr 2022 16:37:18 +0100 Subject: [PATCH 04/20] Add argument type Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/auth/ForgotPassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index cac4aef91ec..5917923b414 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -191,7 +191,7 @@ export default class ForgotPassword extends React.Component { ) }

, button: _t('Continue'), - onFinished: (confirmed) => { + onFinished: (confirmed: boolean) => { if (confirmed) { this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices); } From 564f0fd9eed649ca93a6b07fb17a4d6c06ed81d0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 8 Apr 2022 16:41:14 +0100 Subject: [PATCH 05/20] session => device --- src/components/structures/auth/ForgotPassword.tsx | 8 ++++---- src/i18n/strings/en_EN.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index cac4aef91ec..34570224a6d 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -184,9 +184,9 @@ export default class ForgotPassword extends React.Component { description:
{ _t( - "Signing out all sessions will reset the end-to-end encryption keys " + + "Signing out all devices 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 " + + "Key Backup or export your room keys from another device before resetting your " + "password.", ) }
, @@ -324,7 +324,7 @@ export default class ForgotPassword extends React.Component {
this.setState({ logoutDevices: !this.state.logoutDevices })} checked={this.state.logoutDevices}> - { _t("Sign out all sessions") } + { _t("Sign out all devices") }
{ _t( @@ -368,7 +368,7 @@ export default class ForgotPassword extends React.Component {

{ _t("Your password has been reset.") }

{ this.state.logoutDevices ?

{ _t( - "You have been logged out of all sessions and will no longer receive " + + "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.", ) }

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9e17b563b61..d61c529fba9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3136,19 +3136,19 @@ "Really reset verification keys?": "Really reset verification keys?", "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", - "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.", + "Signing out all devices will reset the end-to-end encryption keys making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Signing out all devices will reset the end-to-end encryption keys making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.", "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", + "Sign out all devices": "Sign out all devices", "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", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", "I have verified my email address": "I have verified my email address", "Your password has been reset.": "Your password has been reset.", - "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.": "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.", + "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.": "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.", "Return to login screen": "Return to login screen", "Set a new password": "Set a new password", "Invalid homeserver discovery response": "Invalid homeserver discovery response", From 5bebf72020e7e56f315f7a5b67aecce0e7727aae Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 8 Apr 2022 16:48:37 +0100 Subject: [PATCH 06/20] Wording changes based on feedback in PR --- src/components/structures/auth/ForgotPassword.tsx | 8 ++++---- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 34570224a6d..aa79b6504ff 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -184,10 +184,10 @@ export default class ForgotPassword extends React.Component { description:
{ _t( - "Signing out all devices will reset the end-to-end encryption keys " + - "making encrypted chat history unreadable. Set up " + - "Key Backup or export your room keys from another device before resetting your " + - "password.", + "Signing out your devices will delete the message encryption keys stored on them, " + + "making encrypted chat history unreadable. If you want to access your encrypted " + + "chat history then set up Key Backup or export your message keys from one of your " + + "other devices before proceeding.", ) }
, button: _t('Continue'), diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d61c529fba9..5cda249634f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3136,7 +3136,7 @@ "Really reset verification keys?": "Really reset verification keys?", "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", - "Signing out all devices will reset the end-to-end encryption keys making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Signing out all devices will reset the end-to-end encryption keys making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.", + "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", "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.", From 68f955150ab35bf7fd118af5a4269fcd18994f28 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 12:09:48 +0100 Subject: [PATCH 07/20] UI to respect if server has capability to control device logout --- .../structures/auth/ForgotPassword.tsx | 45 ++++++++++++++----- src/i18n/strings/en_EN.json | 4 +- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index a13a086f5bb..eb9c5907b55 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -74,6 +74,7 @@ interface IState { currentHttpRequest?: Promise; + serverSupportsControlOfDevicesLogout: boolean; logoutDevices: boolean; } @@ -100,12 +101,14 @@ export default class ForgotPassword extends React.Component { 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 @@ -116,6 +119,9 @@ export default class ForgotPassword extends React.Component { // 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 { @@ -133,6 +139,15 @@ export default class ForgotPassword extends React.Component { } } + private checkServerCapabilities(serverConfig: ValidatedServerConfig) { + // TODO: proper capabilities check + const serverSupportsControlOfDevicesLogout = serverConfig.hsUrl === 'https://matrix-client.matrix.org'; + this.setState({ + logoutDevices: !serverSupportsControlOfDevicesLogout, + serverSupportsControlOfDevicesLogout, + }); + } + public submitPasswordReset(email: string, password: string, logoutDevices = true): void { this.setState({ phase: Phase.SendingEmail, @@ -183,11 +198,19 @@ export default class ForgotPassword extends React.Component { title: _t('Warning!'), description:
- { _t( - "Signing out your devices will delete the message encryption keys stored on them, " + - "making encrypted chat history unreadable. If you want to access your encrypted " + - "chat history then set up Key Backup or export your message keys from one of your " + - "other devices before proceeding.", + { !this.state.serverSupportsControlOfDevicesLogout ? + _t( + "Resetting your password 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("If you want to access your encrypted chat history then set up Key Backup or export " + + "your message keys from one of your other devices before proceeding.", ) }
, button: _t('Continue'), @@ -322,11 +345,13 @@ export default class ForgotPassword extends React.Component { autoComplete="new-password" /> -
- this.setState({ logoutDevices: !this.state.logoutDevices })} checked={this.state.logoutDevices}> - { _t("Sign out all devices") } - -
+ { this.state.serverSupportsControlOfDevicesLogout ? +
+ this.setState({ logoutDevices: !this.state.logoutDevices })} checked={this.state.logoutDevices}> + { _t("Sign out all devices") } + +
: null + } { _t( 'A verification email will be sent to your inbox to confirm ' + 'setting your new password.', diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d495145abe8..895b29bc3d3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3142,7 +3142,9 @@ "Really reset verification keys?": "Really reset verification keys?", "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", - "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", + "Resetting your password 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.": "Resetting your password 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.", + "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", + "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", "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.", From 981813d18d32439037d9b067b30d134d9f07b963 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 12:11:08 +0100 Subject: [PATCH 08/20] Revert "Don't sign out all devices when changing password from Settings tab" This reverts commit ac4b1353ad7c9946e24ad4cf365b3561fc012531. --- .../views/settings/ChangePassword.tsx | 57 +++++++++++++++++-- src/i18n/strings/en_EN.json | 5 +- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index d2b4f178d57..f736e5f6f51 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { ComponentType } from 'react'; import { MatrixClient } from "matrix-js-sdk/src/client"; import Field from "../elements/Field"; @@ -28,6 +28,7 @@ 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'; @@ -46,6 +47,7 @@ interface IProps { buttonClassName?: string; buttonKind?: string; buttonLabel?: string; + confirm?: boolean; // Whether to autoFocus the new password input autoFocusNewPasswordInput?: boolean; className?: string; @@ -64,6 +66,8 @@ export default class ChangePassword extends React.Component { public static defaultProps: Partial = { onFinished() {}, onError() {}, + + confirm: true, }; constructor(props: IProps) { @@ -81,7 +85,42 @@ export default class ChangePassword extends React.Component { private onChangePassword(oldPassword: string, newPassword: string): void { const cli = MatrixClientPeg.get(); - this.changePassword(cli, oldPassword, newPassword); + if (!this.props.confirm) { + this.changePassword(cli, oldPassword, newPassword); + return; + } + + Modal.createTrackedDialog('Change Password', '', QuestionDialog, { + title: _t("Warning!"), + description: +
+ { _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.', + ) } + { ' ' } + + https://github.com/vector-im/element-web/issues/2671 + +
, + button: _t("Continue"), + extraButtons: [ + , + ], + onFinished: (confirmed) => { + if (confirmed) { + this.changePassword(cli, oldPassword, newPassword); + } + }, + }); } private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void { @@ -101,8 +140,7 @@ export default class ChangePassword extends React.Component { phase: Phase.Uploading, }); - // This no longer logs out all sessions: - cli.setPassword(authDict, newPassword, false).then(() => { + cli.setPassword(authDict, newPassword).then(() => { if (this.props.shouldAskForEmail) { return this.optionallySetEmail().then((confirmed) => { this.props.onFinished({ @@ -144,6 +182,17 @@ export default class ChangePassword extends React.Component { 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>, + { + matrixClient: MatrixClientPeg.get(), + }, + ); + }; + private markFieldValid(fieldID: string, valid: boolean): void { const { fieldValid } = this.state; fieldValid[fieldID] = valid; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 895b29bc3d3..2aa4b77c7fc 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1187,6 +1187,9 @@ "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?", @@ -1215,7 +1218,6 @@ "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", "": "", - "Export E2E room keys": "Export E2E room keys", "Import E2E room keys": "Import E2E room keys", "Cryptography": "Cryptography", "Session ID:": "Session ID:", @@ -1999,7 +2001,6 @@ "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?", From e6415b276d1a20e2a6d414aa9b3b66f8f865c690 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 12:47:35 +0100 Subject: [PATCH 09/20] If homeserver supports it then don't sign out all devices when changing password from Settings tab --- .../structures/auth/ForgotPassword.tsx | 2 +- .../views/settings/ChangePassword.tsx | 86 +++++++++++-------- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index eb9c5907b55..444fca76663 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -140,7 +140,7 @@ export default class ForgotPassword extends React.Component { } private checkServerCapabilities(serverConfig: ValidatedServerConfig) { - // TODO: proper capabilities check + // TODO: proper capabilities check - this is just a placeholder const serverSupportsControlOfDevicesLogout = serverConfig.hsUrl === 'https://matrix-client.matrix.org'; this.setState({ logoutDevices: !serverSupportsControlOfDevicesLogout, diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index f736e5f6f51..4123b4a09ef 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -85,45 +85,57 @@ export default class ChangePassword extends React.Component { private onChangePassword(oldPassword: string, newPassword: string): void { const cli = MatrixClientPeg.get(); - if (!this.props.confirm) { - this.changePassword(cli, oldPassword, newPassword); - return; - } + // TODO: proper capabilities check - this is just a placeholder + const serverSupportsControlOfDevicesLogout = MatrixClientPeg.getHomeserverName() === 'matrix.org'; - Modal.createTrackedDialog('Change Password', '', QuestionDialog, { - title: _t("Warning!"), - description: -
- { _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.', - ) } - { ' ' } - - https://github.com/vector-im/element-web/issues/2671 - -
, - button: _t("Continue"), - extraButtons: [ - , - ], - onFinished: (confirmed) => { - if (confirmed) { - this.changePassword(cli, oldPassword, newPassword); - } - }, - }); + if (serverSupportsControlOfDevicesLogout) { + // don't log user out of all devices + this.changePassword(cli, oldPassword, newPassword, /* logoutDevices = */ false); + } else { + if (!this.props.confirm) { + // TODO: should this change to be false rather than undefined? Who uses confirm=false? + this.changePassword(cli, oldPassword, newPassword, /* logoutDevices = */ undefined); + return; + } + + // warn about logging out all devices + Modal.createTrackedDialog('Change Password', '', QuestionDialog, { + title: _t("Warning!"), + description: +
+ { _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, unless you first export your room keys and ' + + 're-import them afterwards. Ask your homeserver admin to upgrade the server to change ' + + 'this behaviour.', + ) } +
, + button: _t("Continue"), + extraButtons: [ + , + ], + onFinished: (confirmed) => { + if (confirmed) { + this.changePassword(cli, oldPassword, newPassword, /* logoutDevices = */ undefined); + } + }, + }); + } } - private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void { + private changePassword( + cli: MatrixClient, + oldPassword: string, + newPassword: string, + logoutDevices: boolean | undefined, + ): void { const authDict = { type: 'm.login.password', identifier: { @@ -140,7 +152,7 @@ export default class ChangePassword extends React.Component { phase: Phase.Uploading, }); - cli.setPassword(authDict, newPassword).then(() => { + cli.setPassword(authDict, newPassword, logoutDevices).then(() => { if (this.props.shouldAskForEmail) { return this.optionallySetEmail().then((confirmed) => { this.props.onFinished({ diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2aa4b77c7fc..bfc4b645480 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1188,7 +1188,7 @@ "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.", + "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, unless you first export your room keys and re-import them afterwards. Ask your homeserver admin to upgrade the server to change this behaviour.": "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, unless you first export your room keys and re-import them afterwards. Ask your homeserver admin to upgrade the server to change this behaviour.", "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", From 5c485202b5a6f459f0d7d1fbc5429444d44bf4a9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 12:54:02 +0100 Subject: [PATCH 10/20] Wording revisions --- src/components/structures/auth/ForgotPassword.tsx | 4 ++-- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 444fca76663..0c41207777b 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -200,8 +200,8 @@ export default class ForgotPassword extends React.Component {
{ !this.state.serverSupportsControlOfDevicesLogout ? _t( - "Resetting your password will cause all of your devices to be signed out." + - "This will delete the message encryption keys stored on them, " + + "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( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bfc4b645480..778e1e5f4f8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3143,7 +3143,7 @@ "Really reset verification keys?": "Really reset verification keys?", "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", - "Resetting your password 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.": "Resetting your password 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.", + "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.": "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.", "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", From fd36f66eae37f4cee5d52939b796f11b183bdd80 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 12:58:36 +0100 Subject: [PATCH 11/20] Whitespace fix --- src/components/structures/auth/ForgotPassword.tsx | 4 ++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 0c41207777b..80cb34d7718 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -202,11 +202,11 @@ export default class ForgotPassword extends React.Component { _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.", + "making encrypted chat history unreadable. ", ) : _t( "Signing out your devices will delete the message encryption keys stored on them, " + - "making encrypted chat history unreadable.", + "making encrypted chat history unreadable. ", ) } { _t("If you want to access your encrypted chat history then set up Key Backup or export " + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 778e1e5f4f8..8951e29b05b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3143,8 +3143,8 @@ "Really reset verification keys?": "Really reset verification keys?", "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", - "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.": "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.", - "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", + "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. ": "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. ", + "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. ": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. ", "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", "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.", From c4380c911c208a439e2c5ce3e99a67eb3fa8a439 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 13:04:58 +0100 Subject: [PATCH 12/20] Remove trailing whitespace from translations --- src/components/structures/auth/ForgotPassword.tsx | 5 +++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 80cb34d7718..9a439f41145 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -202,13 +202,14 @@ export default class ForgotPassword extends React.Component { _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. ", + "making encrypted chat history unreadable.", ) : _t( "Signing out your devices will delete the message encryption keys stored on them, " + - "making encrypted chat history unreadable. ", + "making encrypted chat history unreadable.", ) } + { _t("If you want to access your encrypted chat history then set up Key Backup or export " + "your message keys from one of your other devices before proceeding.", ) } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8951e29b05b..778e1e5f4f8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3143,8 +3143,8 @@ "Really reset verification keys?": "Really reset verification keys?", "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", - "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. ": "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. ", - "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. ": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable. ", + "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.": "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.", + "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", "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.", From e8626ed99d00fa884cdc0d2fd44bed2206743f99 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 14:39:09 +0100 Subject: [PATCH 13/20] Wording and whitespace --- src/components/structures/auth/ForgotPassword.tsx | 12 ++++++------ src/components/views/settings/ChangePassword.tsx | 15 ++++++++++----- src/i18n/strings/en_EN.json | 6 ++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 9a439f41145..01aa9bb0c8f 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -198,7 +198,7 @@ export default class ForgotPassword extends React.Component { title: _t('Warning!'), description:
- { !this.state.serverSupportsControlOfDevicesLogout ? +

{ !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, " + @@ -208,11 +208,11 @@ export default class ForgotPassword extends React.Component { "Signing out your devices will delete the message encryption keys stored on them, " + "making encrypted chat history unreadable.", ) - } - - { _t("If you want to access your encrypted chat history then set up Key Backup or export " + - "your message keys from one of your other devices before proceeding.", - ) } + }

+

{ _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.", + ) }

, button: _t('Continue'), onFinished: (confirmed: boolean) => { diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 4123b4a09ef..4ebaabc1440 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -103,13 +103,18 @@ export default class ChangePassword extends React.Component { title: _t("Warning!"), description:
- { _t( +

{ _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, unless you first export your room keys and ' + - 're-import them afterwards. Ask your homeserver admin to upgrade the server to change ' + - 'this behaviour.', - ) } + 'encrypted chat history unreadable.', + ) }

+

{ _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.', + ) }

+

{ _t( + 'You can also ask your homeserver admin to upgrade the server to change this behaviour.', + ) }

, button: _t("Continue"), extraButtons: [ diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 778e1e5f4f8..d502463f3a9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1188,7 +1188,9 @@ "Upload new:": "Upload new:", "No display name": "No display name", "Warning!": "Warning!", - "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, unless you first export your room keys and re-import them afterwards. Ask your homeserver admin to upgrade the server to change this behaviour.": "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, unless you first export your room keys and re-import them afterwards. Ask your homeserver admin to upgrade the server to change this behaviour.", + "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.": "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.", + "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.": "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.", + "You can also ask your homeserver admin to upgrade the server to change this behaviour.": "You can also ask your homeserver admin to upgrade the server to change this behaviour.", "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", @@ -3145,7 +3147,7 @@ "Failed to send email": "Failed to send email", "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.": "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.", "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", - "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.": "If you want to access your encrypted chat history then set up Key Backup or export your message keys from one of your other devices before proceeding.", + "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.": "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.", "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.", From bd3a12c76e9f3b0516d7eb581d4c800e69b3a1e2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 17:31:43 +0100 Subject: [PATCH 14/20] Add proper capability check based on support spec version --- src/components/structures/auth/ForgotPassword.tsx | 11 ++++++++--- src/components/views/settings/ChangePassword.tsx | 7 +++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 01aa9bb0c8f..0ff592f2dad 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -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"; @@ -139,9 +140,13 @@ export default class ForgotPassword extends React.Component { } } - private checkServerCapabilities(serverConfig: ValidatedServerConfig) { - // TODO: proper capabilities check - this is just a placeholder - const serverSupportsControlOfDevicesLogout = serverConfig.hsUrl === 'https://matrix-client.matrix.org'; + private async checkServerCapabilities(serverConfig: ValidatedServerConfig): Promise { + const tempClient = createClient({ + baseUrl: serverConfig.hsUrl, + }); + + const serverSupportsControlOfDevicesLogout: boolean = await tempClient.doesServerSupportLogoutDevices(); + this.setState({ logoutDevices: !serverSupportsControlOfDevicesLogout, serverSupportsControlOfDevicesLogout, diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 4ebaabc1440..8b298717fcc 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -82,11 +82,10 @@ export default class ChangePassword extends React.Component { }; } - private onChangePassword(oldPassword: string, newPassword: string): void { + private async onChangePassword(oldPassword: string, newPassword: string): Promise { const cli = MatrixClientPeg.get(); - // TODO: proper capabilities check - this is just a placeholder - const serverSupportsControlOfDevicesLogout = MatrixClientPeg.getHomeserverName() === 'matrix.org'; + const serverSupportsControlOfDevicesLogout: boolean = await cli.doesServerSupportLogoutDevices(); if (serverSupportsControlOfDevicesLogout) { // don't log user out of all devices @@ -296,7 +295,7 @@ export default class ChangePassword extends React.Component { if (err) { this.props.onError(err); } else { - this.onChangePassword(oldPassword, newPassword); + return this.onChangePassword(oldPassword, newPassword); } }; From 36c6fdebf6e5e0454bd8949a01379fe5662494dc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 18:44:30 +0100 Subject: [PATCH 15/20] Update src/components/views/settings/ChangePassword.tsx Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/settings/ChangePassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 8b298717fcc..ea94e01c2be 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -85,7 +85,7 @@ export default class ChangePassword extends React.Component { private async onChangePassword(oldPassword: string, newPassword: string): Promise { const cli = MatrixClientPeg.get(); - const serverSupportsControlOfDevicesLogout: boolean = await cli.doesServerSupportLogoutDevices(); + const serverSupportsControlOfDevicesLogout = await cli.doesServerSupportLogoutDevices(); if (serverSupportsControlOfDevicesLogout) { // don't log user out of all devices From 20b33166792a16eb779e800c39fd9c747c878c62 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 18:44:45 +0100 Subject: [PATCH 16/20] Update src/components/structures/auth/ForgotPassword.tsx Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/auth/ForgotPassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 0ff592f2dad..6498c9ebedc 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -145,7 +145,7 @@ export default class ForgotPassword extends React.Component { baseUrl: serverConfig.hsUrl, }); - const serverSupportsControlOfDevicesLogout: boolean = await tempClient.doesServerSupportLogoutDevices(); + const serverSupportsControlOfDevicesLogout = await tempClient.doesServerSupportLogoutDevices(); this.setState({ logoutDevices: !serverSupportsControlOfDevicesLogout, From cff80ac61c1c094cdbc8db5c508d63384132f007 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 13 Apr 2022 19:00:14 +0100 Subject: [PATCH 17/20] Refactor to use Modal promises --- .../structures/auth/ForgotPassword.tsx | 14 +++++------ .../views/settings/ChangePassword.tsx | 25 +++++++------------ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 6498c9ebedc..5994ac27621 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -199,7 +199,7 @@ export default class ForgotPassword extends React.Component { } if (this.state.logoutDevices) { - Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, { + const { finished } = Modal.createTrackedDialog<[boolean]>('Forgot Password Warning', '', QuestionDialog, { title: _t('Warning!'), description:
@@ -220,15 +220,13 @@ export default class ForgotPassword extends React.Component { ) }

, button: _t('Continue'), - onFinished: (confirmed: boolean) => { - if (confirmed) { - this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices); - } - }, }); - } else { - this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices); + const [confirmed] = await finished; + + if (!confirmed) return; } + + this.submitPasswordReset(this.state.email, this.state.password, this.state.logoutDevices); }; private async verifyFieldsBeforeSubmit() { diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index ea94e01c2be..6b068c58f97 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -85,20 +85,13 @@ export default class ChangePassword extends React.Component { private async onChangePassword(oldPassword: string, newPassword: string): Promise { const cli = MatrixClientPeg.get(); + // if the server supports it then don't sign user out of all devices const serverSupportsControlOfDevicesLogout = await cli.doesServerSupportLogoutDevices(); + const logoutDevices = serverSupportsControlOfDevicesLogout ? false : undefined; - if (serverSupportsControlOfDevicesLogout) { - // don't log user out of all devices - this.changePassword(cli, oldPassword, newPassword, /* logoutDevices = */ false); - } else { - if (!this.props.confirm) { - // TODO: should this change to be false rather than undefined? Who uses confirm=false? - this.changePassword(cli, oldPassword, newPassword, /* logoutDevices = */ undefined); - return; - } - + if (!serverSupportsControlOfDevicesLogout && this.props.confirm) { // warn about logging out all devices - Modal.createTrackedDialog('Change Password', '', QuestionDialog, { + const { finished } = Modal.createTrackedDialog<[boolean]>('Change Password', '', QuestionDialog, { title: _t("Warning!"), description:
@@ -125,13 +118,13 @@ export default class ChangePassword extends React.Component { { _t('Export E2E room keys') } , ], - onFinished: (confirmed) => { - if (confirmed) { - this.changePassword(cli, oldPassword, newPassword, /* logoutDevices = */ undefined); - } - }, }); + + const [confirmed] = await finished; + if (!confirmed) return; } + + this.changePassword(cli, oldPassword, newPassword, logoutDevices); } private changePassword( From fe384cd6ce977a0710a8a5aeb54ac264b62b74d2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 19 Apr 2022 10:57:34 +0100 Subject: [PATCH 18/20] Take account of whether devices where signed out in password change confirmation --- src/components/views/settings/ChangePassword.tsx | 8 ++++++-- .../settings/tabs/user/GeneralUserSettingsTab.tsx | 13 ++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 6b068c58f97..08ec38da1f7 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -41,7 +41,7 @@ enum Phase { } interface IProps { - onFinished?: ({ didSetEmail: boolean }?) => void; + onFinished?: (outcome: { didSetEmail?: boolean, didLogoutAllDevices: boolean }) => void; onError?: (error: {error: string}) => void; rowClassName?: string; buttonClassName?: string; @@ -149,15 +149,19 @@ export default class ChangePassword extends React.Component { phase: Phase.Uploading, }); + // undefined or true mean all devices signed out + const didLogoutAllDevices = logoutDevices !== false; + cli.setPassword(authDict, newPassword, logoutDevices).then(() => { if (this.props.shouldAskForEmail) { return this.optionallySetEmail().then((confirmed) => { this.props.onFinished({ didSetEmail: confirmed, + didLogoutAllDevices, }); }); } else { - this.props.onFinished(); + this.props.onFinished({ didLogoutAllDevices }); } }, (err) => { this.props.onError(err); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 7db27b009e5..abcf5a77861 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -260,14 +260,17 @@ export default class GeneralUserSettingsTab extends React.Component { + private onPasswordChanged = ({ didLogoutAllDevices }: { didLogoutAllDevices: boolean }): void => { + let description = _t("Your password was successfully changed."); + if (didLogoutAllDevices) { + 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, }); }; From f3260a00e684849a161929d8e383e5a23d3a83ab Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 19 Apr 2022 10:59:06 +0100 Subject: [PATCH 19/20] Update translations --- src/i18n/strings/en_EN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d502463f3a9..2199af012fe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1403,8 +1403,9 @@ "Customise your appearance": "Customise your appearance", "Appearance Settings only affect this %(brand)s session.": "Appearance Settings only affect this %(brand)s session.", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", + "Your password was successfully changed.": "Your password was successfully changed.", + "You will not receive push notifications on other devices until you sign back in to them.": "You will not receive push notifications on other devices until you sign back in to them.", "Success": "Success", - "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them", "Email addresses": "Email addresses", "Phone numbers": "Phone numbers", "Set a new account password...": "Set a new account password...", From 9038ea79f416cc8680d1ce7bf9484bfb90ab3c67 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 21 Apr 2022 18:17:10 +0100 Subject: [PATCH 20/20] Only warn user when changing password in Settings if they have other devices --- .../views/settings/ChangePassword.tsx | 23 ++++++++++++------- .../tabs/user/GeneralUserSettingsTab.tsx | 4 ++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 08ec38da1f7..39e7ff8d1d0 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -41,7 +41,11 @@ enum Phase { } interface IProps { - onFinished?: (outcome: { didSetEmail?: boolean, didLogoutAllDevices: 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; @@ -87,9 +91,9 @@ export default class ChangePassword extends React.Component { // if the server supports it then don't sign user out of all devices const serverSupportsControlOfDevicesLogout = await cli.doesServerSupportLogoutDevices(); - const logoutDevices = serverSupportsControlOfDevicesLogout ? false : undefined; + const userHasOtherDevices = (await cli.getDevices()).devices.length > 1; - if (!serverSupportsControlOfDevicesLogout && this.props.confirm) { + if (userHasOtherDevices && !serverSupportsControlOfDevicesLogout && this.props.confirm) { // warn about logging out all devices const { finished } = Modal.createTrackedDialog<[boolean]>('Change Password', '', QuestionDialog, { title: _t("Warning!"), @@ -124,14 +128,15 @@ export default class ChangePassword extends React.Component { if (!confirmed) return; } - this.changePassword(cli, oldPassword, newPassword, logoutDevices); + this.changePassword(cli, oldPassword, newPassword, serverSupportsControlOfDevicesLogout, userHasOtherDevices); } private changePassword( cli: MatrixClient, oldPassword: string, newPassword: string, - logoutDevices: boolean | undefined, + serverSupportsControlOfDevicesLogout: boolean, + userHasOtherDevices: boolean, ): void { const authDict = { type: 'm.login.password', @@ -149,19 +154,21 @@ export default class ChangePassword extends React.Component { phase: Phase.Uploading, }); + const logoutDevices = serverSupportsControlOfDevicesLogout ? false : undefined; + // undefined or true mean all devices signed out - const didLogoutAllDevices = logoutDevices !== false; + const didLogoutOutOtherDevices = !serverSupportsControlOfDevicesLogout && userHasOtherDevices; cli.setPassword(authDict, newPassword, logoutDevices).then(() => { if (this.props.shouldAskForEmail) { return this.optionallySetEmail().then((confirmed) => { this.props.onFinished({ didSetEmail: confirmed, - didLogoutAllDevices, + didLogoutOutOtherDevices, }); }); } else { - this.props.onFinished({ didLogoutAllDevices }); + this.props.onFinished({ didLogoutOutOtherDevices }); } }, (err) => { this.props.onError(err); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index abcf5a77861..36d3e55d852 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -260,9 +260,9 @@ export default class GeneralUserSettingsTab extends React.Component { + private onPasswordChanged = ({ didLogoutOutOtherDevices }: { didLogoutOutOtherDevices: boolean }): void => { let description = _t("Your password was successfully changed."); - if (didLogoutAllDevices) { + if (didLogoutOutOtherDevices) { description += " " + _t( "You will not receive push notifications on other devices until you sign back in to them.", );