diff --git a/src/components/views/dialogs/DeactivateAccountDialog.js b/src/components/views/dialogs/DeactivateAccountDialog.js index 4c14f356e4..3889f0989a 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.js +++ b/src/components/views/dialogs/DeactivateAccountDialog.js @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,71 +23,109 @@ import Analytics from '../../../Analytics'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import * as Lifecycle from '../../../Lifecycle'; import { _t } from '../../../languageHandler'; +import InteractiveAuth, {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth"; +import {DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents"; + +const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."), + continueText: _t("Single Sign On"), + continueKind: "danger", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + body: _t("Are you sure you want to deactivate your account? This is irreversible."), + continueText: _t("Confirm account deactivation"), + continueKind: "danger", + }, +}; + +// This is the same as aestheticsForStagePhases in InteractiveAuthDialog minus the `title` +const DEACTIVATE_AESTHETICS = { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + [PasswordAuthEntry.LOGIN_TYPE]: { + [DEFAULT_PHASE]: { + body: _t("To continue, please enter your password:"), + }, + }, +}; export default class DeactivateAccountDialog extends React.Component { constructor(props) { super(props); - this._onOk = this._onOk.bind(this); - this._onCancel = this._onCancel.bind(this); - this._onPasswordFieldChange = this._onPasswordFieldChange.bind(this); - this._onEraseFieldChange = this._onEraseFieldChange.bind(this); - this.state = { - password: "", - busy: false, shouldErase: false, errStr: null, + authData: null, // for UIA + + // A few strings that are passed to InteractiveAuth for design or are displayed + // next to the InteractiveAuth component. + bodyText: null, + continueText: null, + continueKind: null, }; - } - _onPasswordFieldChange(ev) { - this.setState({ - password: ev.target.value, - }); - } - - _onEraseFieldChange(ev) { - this.setState({ - shouldErase: ev.target.checked, - }); - } - - async _onOk() { - this.setState({busy: true}); - - try { - // This assumes that the HS requires password UI auth - // for this endpoint. In reality it could be any UI auth. - const auth = { - type: 'm.login.password', - // TODO: Remove `user` once servers support proper UIA - // See https://github.com/vector-im/riot-web/issues/10312 - user: MatrixClientPeg.get().credentials.userId, - identifier: { - type: "m.id.user", - user: MatrixClientPeg.get().credentials.userId, - }, - password: this.state.password, - }; - await MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase); - } catch (err) { - let errStr = _t('Unknown error'); - // https://matrix.org/jira/browse/SYN-744 - if (err.httpStatus === 401 || err.httpStatus === 403) { - errStr = _t('Incorrect password'); + MatrixClientPeg.get().deactivateAccount(null, false).then(r => { + // If we got here, oops. The server didn't require any auth. + // Our application lifecycle will catch the error and do the logout bits. + // We'll try to log something in an vain attempt to record what happened (storage + // is also obliterated on logout). + console.warn("User's account got deactivated without confirmation: Server had no auth"); + this.setState({errStr: _t("Server did not require any authentication")}); + }).catch(e => { + if (e && e.httpStatus === 401 && e.data) { + // Valid UIA response + this.setState({authData: e.data}); + } else { + this.setState({errStr: _t("Server did not return valid authentication information.")}); } - this.setState({ - busy: false, - errStr: errStr, - }); + }); + } + + _onStagePhaseChange = (stage, phase) => { + const aesthetics = DEACTIVATE_AESTHETICS[stage]; + let bodyText = null; + let continueText = null; + let continueKind = null; + if (aesthetics) { + const phaseAesthetics = aesthetics[phase]; + if (phaseAesthetics && phaseAesthetics.body) bodyText = phaseAesthetics.body; + if (phaseAesthetics && phaseAesthetics.continueText) continueText = phaseAesthetics.continueText; + if (phaseAesthetics && phaseAesthetics.continueKind) continueKind = phaseAesthetics.continueKind; + } + this.setState({bodyText, continueText, continueKind}); + }; + + _onUIAuthFinished = (success, result, extra) => { + if (success) return; // great! makeRequest() will be called too. + + if (result === ERROR_USER_CANCELLED) { + this._onCancel(); return; } - Analytics.trackEvent('Account', 'Deactivate Account'); - Lifecycle.onLoggedOut(); - this.props.onFinished(true); - } + console.error("Error during UI Auth:", {result, extra}); + this.setState({errStr: _t("There was a problem communicating with the server. Please try again.")}); + }; + + _onUIAuthComplete = (auth) => { + MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => { + // Deactivation worked - logout & close this dialog + Analytics.trackEvent('Account', 'Deactivate Account'); + Lifecycle.onLoggedOut(); + this.props.onFinished(true); + }).catch(e => { + console.error(e); + this.setState({errStr: _t("There was a problem communicating with the server. Please try again.")}); + }); + }; + + _onEraseFieldChange = (ev) => { + this.setState({ + shouldErase: ev.target.checked, + }); + }; _onCancel() { this.props.onFinished(false); @@ -95,34 +133,36 @@ export default class DeactivateAccountDialog extends React.Component { render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const Loader = sdk.getComponent("elements.Spinner"); - let passwordBoxClass = ''; let error = null; if (this.state.errStr) { error =
{ this.state.errStr }
; - passwordBoxClass = 'error'; } - const okLabel = this.state.busy ? : _t('Deactivate Account'); - const okEnabled = this.state.password && !this.state.busy; - - let cancelButton = null; - if (!this.state.busy) { - cancelButton = ; + let auth =
{_t("Loading...")}
; + if (this.state.authData) { + auth = ( +
+ {this.state.bodyText} + +
+ ); } - const Field = sdk.getComponent('elements.Field'); - // this is on purpose not a
to prevent Enter triggering submission, to further prevent accidents return ( @@ -172,28 +212,10 @@ export default class DeactivateAccountDialog extends React.Component {

-

{ _t("To continue, please enter your password:") }

- + {error} + {auth} - { error } - -
- - - { cancelButton }
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 004e81da71..e653942c10 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1571,13 +1571,17 @@ "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", "Incompatible Database": "Incompatible Database", "Continue With Encryption Disabled": "Continue With Encryption Disabled", - "Unknown error": "Unknown error", - "Incorrect password": "Incorrect password", + "Confirm your account deactivation by using Single Sign On to prove your identity.": "Confirm your account deactivation by using Single Sign On to prove your identity.", + "Are you sure you want to deactivate your account? This is irreversible.": "Are you sure you want to deactivate your account? This is irreversible.", + "Confirm account deactivation": "Confirm account deactivation", + "To continue, please enter your password:": "To continue, please enter your password:", + "Server did not require any authentication": "Server did not require any authentication", + "Server did not return valid authentication information.": "Server did not return valid authentication information.", + "There was a problem communicating with the server. Please try again.": "There was a problem communicating with the server. Please try again.", "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.", "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", - "To continue, please enter your password:": "To continue, please enter your password:", "Verify session": "Verify session", "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", "Verify by comparing a short text string.": "Verify by comparing a short text string.", @@ -1973,6 +1977,7 @@ "Failed to leave room": "Failed to leave room", "Can't leave Server Notices room": "Can't leave Server Notices room", "This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.", + "Unknown error": "Unknown error", "Signed Out": "Signed Out", "For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.", "Terms and Conditions": "Terms and Conditions", @@ -2112,6 +2117,7 @@ "Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.", "Go Back": "Go Back", "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", + "Incorrect password": "Incorrect password", "Failed to re-authenticate": "Failed to re-authenticate", "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.", "Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.",