diff --git a/src/component-index.js b/src/component-index.js index 5b28be0627..b357472ad7 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -31,6 +31,8 @@ import structures$CreateRoom from './components/structures/CreateRoom'; structures$CreateRoom && (module.exports.components['structures.CreateRoom'] = structures$CreateRoom); import structures$FilePanel from './components/structures/FilePanel'; structures$FilePanel && (module.exports.components['structures.FilePanel'] = structures$FilePanel); +import structures$InteractiveAuth from './components/structures/InteractiveAuth'; +structures$InteractiveAuth && (module.exports.components['structures.InteractiveAuth'] = structures$InteractiveAuth); import structures$LoggedInView from './components/structures/LoggedInView'; structures$LoggedInView && (module.exports.components['structures.LoggedInView'] = structures$LoggedInView); import structures$MatrixChat from './components/structures/MatrixChat'; diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js new file mode 100644 index 0000000000..205d2ca1b8 --- /dev/null +++ b/src/components/structures/InteractiveAuth.js @@ -0,0 +1,153 @@ +/* +Copyright 2017 Vector Creations 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 Matrix from 'matrix-js-sdk'; +const InteractiveAuth = Matrix.InteractiveAuth; + +import React from 'react'; + +import sdk from '../../index'; + +import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents'; + +export default React.createClass({ + displayName: 'InteractiveAuth', + + propTypes: { + // response from initial request. If not supplied, will do a request on + // mount. + authData: React.PropTypes.shape({ + flows: React.PropTypes.array, + params: React.PropTypes.object, + session: React.PropTypes.string, + }), + + // callback + makeRequest: React.PropTypes.func.isRequired, + + onFinished: React.PropTypes.func.isRequired, + }, + + getInitialState: function() { + return { + authStage: null, + busy: false, + errorText: null, + stageErrorText: null, + submitButtonEnabled: false, + }; + }, + + componentWillMount: function() { + this._unmounted = false; + this._authLogic = new InteractiveAuth({ + authData: this.props.authData, + doRequest: this._requestCallback, + startAuthStage: this._startAuthStage, + }); + + this._authLogic.attemptAuth().then((result) => { + this.props.onFinished(true, result); + }).catch((error) => { + console.error("Error during user-interactive auth:", error); + if (this._unmounted) { + return; + } + + const msg = error.message || error.toString(); + this.setState({ + errorText: msg + }); + }).done(); + }, + + componentWillUnmount: function() { + this._unmounted = true; + }, + + _startAuthStage: function(stageType, error) { + this.setState({ + authStage: stageType, + errorText: error ? error.error : null, + }, this._setFocus); + }, + + _requestCallback: function(auth) { + this.setState({ + busy: true, + errorText: null, + stageErrorText: null, + }); + return this.props.makeRequest(auth).finally(() => { + if (this._unmounted) { + return; + } + this.setState({ + busy: false, + }); + }); + }, + + _setFocus: function() { + if (this.refs.stageComponent && this.refs.stageComponent.focus) { + this.refs.stageComponent.focus(); + } + }, + + _onCancel: function() { + this.props.onFinished(false); + }, + + _submitAuthDict: function(authData) { + this._authLogic.submitAuthDict(authData); + }, + + _renderCurrentStage: function() { + const stage = this.state.authStage; + var StageComponent = getEntryComponentForLoginType(stage); + return ( + + ); + }, + + render: function() { + const Loader = sdk.getComponent("elements.Spinner"); + + let error = null; + if (this.state.errorText) { + error = ( +
+ {this.state.errorText} +
+ ); + } + + return ( +
+
+ {this._renderCurrentStage()} + {error} +
+
+ ); + }, +}); diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index 2b3980c536..01a16e86ac 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import * as KeyCode from '../../../KeyCode'; +import AccessibleButton from '../elements/AccessibleButton'; /** * Basic container for modal dialogs. @@ -59,9 +60,23 @@ export default React.createClass({ } }, + _onCancelClick: function(e) { + e.stopPropagation(); + e.preventDefault(); + this.props.onFinished(); + }, + render: function() { return (
+ + Cancel +
{ this.props.title }
diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js index a4abbb17d9..1c9c872070 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.js +++ b/src/components/views/dialogs/InteractiveAuthDialog.js @@ -15,13 +15,12 @@ limitations under the License. */ import Matrix from 'matrix-js-sdk'; -const InteractiveAuth = Matrix.InteractiveAuth; import React from 'react'; import sdk from '../../../index'; -import {getEntryComponentForLoginType} from '../login/InteractiveAuthEntryComponents'; +import AccessibleButton from '../elements/AccessibleButton'; export default React.createClass({ displayName: 'InteractiveAuthDialog', @@ -41,168 +40,33 @@ export default React.createClass({ onFinished: React.PropTypes.func.isRequired, title: React.PropTypes.string, - submitButtonLabel: React.PropTypes.string, }, getDefaultProps: function() { return { title: "Authentication", - submitButtonLabel: "Submit", }; }, - getInitialState: function() { - return { - authStage: null, - busy: false, - errorText: null, - stageErrorText: null, - submitButtonEnabled: false, - }; - }, - - componentWillMount: function() { - this._unmounted = false; - this._authLogic = new InteractiveAuth({ - authData: this.props.authData, - doRequest: this._requestCallback, - startAuthStage: this._startAuthStage, - }); - - this._authLogic.attemptAuth().then((result) => { - this.props.onFinished(true, result); - }).catch((error) => { - console.error("Error during user-interactive auth:", error); - if (this._unmounted) { - return; - } - - const msg = error.message || error.toString(); - this.setState({ - errorText: msg - }); - }).done(); - }, - - componentWillUnmount: function() { - this._unmounted = true; - }, - - _startAuthStage: function(stageType, error) { - this.setState({ - authStage: stageType, - errorText: error ? error.error : null, - }, this._setFocus); - }, - - _requestCallback: function(auth) { - this.setState({ - busy: true, - errorText: null, - stageErrorText: null, - }); - return this.props.makeRequest(auth).finally(() => { - if (this._unmounted) { - return; - } - this.setState({ - busy: false, - }); - }); - }, - - _onEnterPressed: function(e) { - if (this.state.submitButtonEnabled && !this.state.busy) { - this._onSubmit(); - } - }, - - _onSubmit: function() { - if (this.refs.stageComponent && this.refs.stageComponent.onSubmitClick) { - this.refs.stageComponent.onSubmitClick(); - } - }, - - _setFocus: function() { - if (this.refs.stageComponent && this.refs.stageComponent.focus) { - this.refs.stageComponent.focus(); - } - }, - - _onCancel: function() { + _onCancelClick: function() { this.props.onFinished(false); }, - _setSubmitButtonEnabled: function(enabled) { - this.setState({ - submitButtonEnabled: enabled, - }); - }, - - _submitAuthDict: function(authData) { - this._authLogic.submitAuthDict(authData); - }, - - _renderCurrentStage: function() { - const stage = this.state.authStage; - var StageComponent = getEntryComponentForLoginType(stage); - return ( - - ); - }, - render: function() { - const Loader = sdk.getComponent("elements.Spinner"); + const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth"); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - let error = null; - if (this.state.errorText) { - error = ( -
- {this.state.errorText} -
- ); - } - - const submitLabel = this.state.busy ? : this.props.submitButtonLabel; - const submitEnabled = this.state.submitButtonEnabled && !this.state.busy; - - const submitButton = ( - - ); - - const cancelButton = ( - - ); - return ( -
-

This operation requires additional authentication.

- {this._renderCurrentStage()} - {error} -
-
- {submitButton} - {cancelButton} +
+
); diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js index ec184ca09f..a8c58ea76f 100644 --- a/src/components/views/login/InteractiveAuthEntryComponents.js +++ b/src/components/views/login/InteractiveAuthEntryComponents.js @@ -32,7 +32,6 @@ import MatrixClientPeg from '../../../MatrixClientPeg'; * stageParams: params from the server for the stage being attempted * errorText: error message from a previous attempt to authenticate * submitAuthDict: a function which will be called with the new auth dict - * setSubmitButtonEnabled: a function which will enable/disable the 'submit' button * * Each component may also provide the following functions (beyond the standard React ones): * onSubmitClick: handle a 'submit' button click @@ -48,12 +47,13 @@ export const PasswordAuthEntry = React.createClass({ propTypes: { submitAuthDict: React.PropTypes.func.isRequired, - setSubmitButtonEnabled: React.PropTypes.func.isRequired, errorText: React.PropTypes.string, }, - componentWillMount: function() { - this.props.setSubmitButtonEnabled(false); + getInitialState: function() { + return { + enableSubmit: false, + }; }, focus: function() { @@ -62,7 +62,7 @@ export const PasswordAuthEntry = React.createClass({ } }, - onSubmitClick: function() { + _onSubmit: function() { this.props.submitAuthDict({ type: PasswordAuthEntry.LOGIN_TYPE, user: MatrixClientPeg.get().credentials.userId, @@ -72,7 +72,9 @@ export const PasswordAuthEntry = React.createClass({ _onPasswordFieldChange: function(ev) { // enable the submit button iff the password is non-empty - this.props.setSubmitButtonEnabled(Boolean(ev.target.value)); + this.setState({ + enableSubmit: Boolean(this.refs.passwordField.value), + }); }, render: function() { @@ -86,12 +88,20 @@ export const PasswordAuthEntry = React.createClass({

To continue, please enter your password.

Password:

- +
+ +
+ +
+
{this.props.errorText}
@@ -110,14 +120,9 @@ export const RecaptchaAuthEntry = React.createClass({ propTypes: { submitAuthDict: React.PropTypes.func.isRequired, stageParams: React.PropTypes.object.isRequired, - setSubmitButtonEnabled: React.PropTypes.func.isRequired, errorText: React.PropTypes.string, }, - componentWillMount: function() { - this.props.setSubmitButtonEnabled(false); - }, - _onCaptchaResponse: function(response) { this.props.submitAuthDict({ type: RecaptchaAuthEntry.LOGIN_TYPE, @@ -148,7 +153,6 @@ export const FallbackAuthEntry = React.createClass({ authSessionId: React.PropTypes.string.isRequired, loginType: React.PropTypes.string.isRequired, submitAuthDict: React.PropTypes.func.isRequired, - setSubmitButtonEnabled: React.PropTypes.func.isRequired, errorText: React.PropTypes.string, }, @@ -156,7 +160,6 @@ export const FallbackAuthEntry = React.createClass({ // we have to make the user click a button, as browsers will block // the popup if we open it immediately. this._popupWindow = null; - this.props.setSubmitButtonEnabled(true); window.addEventListener("message", this._onReceiveMessage); }, @@ -173,7 +176,6 @@ export const FallbackAuthEntry = React.createClass({ this.props.authSessionId ); this._popupWindow = window.open(url); - this.props.setSubmitButtonEnabled(false); }, _onReceiveMessage: function(event) {