diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 7c507c2c50..23bf5f60a5 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -42,11 +42,11 @@ function logout() { // logout doesn't work for guest sessions // Also we sometimes want to re-log in a guest session // if we abort the login - _onLoggedOut(); + onLoggedOut(); return; } - return MatrixClientPeg.get().logout().then(_onLoggedOut, + return MatrixClientPeg.get().logout().then(onLoggedOut, (err) => { // Just throwing an error here is going to be very unhelpful // if you're trying to log out because your server's down and @@ -56,7 +56,7 @@ function logout() { // tokens expire (and if you really think you've been compromised, // change your password). console.log("Failed to call logout API: token will not be invalidated"); - _onLoggedOut(); + onLoggedOut(); } ); } @@ -79,7 +79,11 @@ function startMatrixClient() { MatrixClientPeg.start(); } -function _onLoggedOut() { +/* + * Stops a running client and all related services, used after + * a session has been logged out / ended. + */ +function onLoggedOut() { if (window.localStorage) { const hsUrl = window.localStorage.getItem("mx_hs_url"); const isUrl = window.localStorage.getItem("mx_is_url"); @@ -95,7 +99,9 @@ function _onLoggedOut() { dis.dispatch({action: 'on_logged_out'}); } -// stop all the background processes related to the current client +/** + * Stop all the background processes related to the current client + */ function _stopMatrixClient() { Notifier.stop(); UserActivity.stop(); @@ -106,5 +112,5 @@ function _stopMatrixClient() { } module.exports = { - setLoggedIn, logout, startMatrixClient + setLoggedIn, logout, startMatrixClient, onLoggedOut }; diff --git a/src/component-index.js b/src/component-index.js index ca8887858c..97f8882b82 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -45,6 +45,7 @@ module.exports.components['views.avatars.RoomAvatar'] = require('./components/vi module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton'); module.exports.components['views.create_room.Presets'] = require('./components/views/create_room/Presets'); module.exports.components['views.create_room.RoomAlias'] = require('./components/views/create_room/RoomAlias'); +module.exports.components['views.dialogs.DeactivateAccountDialog'] = require('./components/views/dialogs/DeactivateAccountDialog'); module.exports.components['views.dialogs.ErrorDialog'] = require('./components/views/dialogs/ErrorDialog'); module.exports.components['views.dialogs.LogoutPrompt'] = require('./components/views/dialogs/LogoutPrompt'); module.exports.components['views.dialogs.NeedToRegisterDialog'] = require('./components/views/dialogs/NeedToRegisterDialog'); diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 6555668ff4..89146229fa 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -262,6 +262,11 @@ module.exports = React.createClass({ }); }, + _onDeactivateAccountClicked: function() { + const DeactivateAccountDialog = sdk.getComponent("dialogs.DeactivateAccountDialog"); + Modal.createDialog(DeactivateAccountDialog, {}); + }, + _renderUserInterfaceSettings: function() { var client = MatrixClientPeg.get(); @@ -379,6 +384,20 @@ module.exports = React.createClass({ ) }, + _renderDeactivateAccount: function() { + // We can't deactivate a guest account. + if (MatrixClientPeg.get().isGuest()) return null; + + return
+

Deactivate Account

+
+ +
+
; + }, + render: function() { var self = this; var Loader = sdk.getComponent("elements.Spinner"); @@ -548,6 +567,8 @@ module.exports = React.createClass({ + {this._renderDeactivateAccount()} + ); diff --git a/src/components/views/dialogs/DeactivateAccountDialog.js b/src/components/views/dialogs/DeactivateAccountDialog.js new file mode 100644 index 0000000000..926e4059d2 --- /dev/null +++ b/src/components/views/dialogs/DeactivateAccountDialog.js @@ -0,0 +1,137 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import Lifecycle from '../../../Lifecycle'; +import Velocity from 'velocity-vector'; + +export default class DeactivateAccountDialog extends React.Component { + constructor(props, context) { + super(props, context); + + this._passwordField = null; + + this._onOk = this._onOk.bind(this); + this._onCancel = this._onCancel.bind(this); + this._onPasswordFieldChange = this._onPasswordFieldChange.bind(this); + + this.state = { + confirmButtonEnabled: false, + busy: false, + errStr: null, + }; + } + + _onPasswordFieldChange(ev) { + this.setState({ + confirmButtonEnabled: Boolean(ev.target.value), + }); + } + + _onOk() { + // This assumes that the HS requires password UI auth + // for this endpoint. In reality it could be any UI auth. + this.setState({busy: true}); + MatrixClientPeg.get().deactivateAccount({ + type: 'm.login.password', + user: MatrixClientPeg.get().credentials.userId, + password: this._passwordField.value, + }).done(() => { + Lifecycle.onLoggedOut(); + this.props.onFinished(false); + }, (err) => { + let errStr = 'Unknown error'; + // https://matrix.org/jira/browse/SYN-744 + if (err.httpStatus == 401 || err.httpStatus == 403) { + errStr = 'Incorrect password'; + Velocity(this._passwordField, "callout.shake", 300); + } + this.setState({ + busy: false, + errStr: errStr, + }); + }); + } + + _onCancel() { + this.props.onFinished(false); + } + + render() { + const Loader = sdk.getComponent("elements.Spinner"); + let passwordBoxClass = ''; + + let error = null; + if (this.state.errStr) { + error =
+ {this.state.err_str} +
+ passwordBoxClass = 'error'; + } + + const okLabel = this.state.busy ? : 'Deactivate Account'; + const okEnabled = this.state.confirmButtonEnabled && !this.state.busy; + + let cancelButton = null; + if (!this.state.busy) { + cancelButton = + } + + return ( +
+
+ Deactivate Account +
+
+

This will make your account permanently unusable. You will not be able to re-register the same user ID.

+ +

This action is irreversible.

+ +

To continue, please enter your password.

+ +

Password:

+ {this._passwordField = e;}} + className={passwordBoxClass} + /> + {error} +
+
+ + + {cancelButton} +
+
+ ); + } +} + +DeactivateAccountDialog.propTypes = { + onFinished: React.PropTypes.func.isRequired, +};