2016-10-11 20:04:55 +03:00
|
|
|
/*
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
2017-02-13 19:15:00 +03:00
|
|
|
Copyright 2017 Vector Creations Ltd
|
2016-10-11 20:04:55 +03:00
|
|
|
|
|
|
|
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';
|
2017-03-14 14:50:13 +03:00
|
|
|
import url from 'url';
|
|
|
|
import classnames from 'classnames';
|
2016-10-11 20:04:55 +03:00
|
|
|
|
|
|
|
import sdk from '../../../index';
|
2017-05-30 17:09:57 +03:00
|
|
|
import { _t } from '../../../languageHandler';
|
2016-10-11 20:04:55 +03:00
|
|
|
|
|
|
|
/* This file contains a collection of components which are used by the
|
2017-02-13 22:09:43 +03:00
|
|
|
* InteractiveAuth to prompt the user to enter the information needed
|
2016-10-11 20:04:55 +03:00
|
|
|
* for an auth stage. (The intention is that they could also be used for other
|
|
|
|
* components, such as the registration flow).
|
|
|
|
*
|
|
|
|
* Call getEntryComponentForLoginType() to get a component suitable for a
|
|
|
|
* particular login type. Each component requires the same properties:
|
|
|
|
*
|
2017-02-24 14:41:23 +03:00
|
|
|
* matrixClient: A matrix client. May be a different one to the one
|
|
|
|
* currently being used generally (eg. to register with
|
|
|
|
* one HS whilst beign a guest on another).
|
2016-10-11 20:04:55 +03:00
|
|
|
* loginType: the login type of the auth stage being attempted
|
|
|
|
* authSessionId: session id from the server
|
2017-03-01 19:04:15 +03:00
|
|
|
* clientSecret: The client secret in use for ID server auth sessions
|
2016-10-11 20:04:55 +03:00
|
|
|
* 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
|
2017-02-14 13:34:43 +03:00
|
|
|
* busy: a boolean indicating whether the auth logic is doing something
|
|
|
|
* the user needs to wait for.
|
2017-03-01 19:04:15 +03:00
|
|
|
* inputs: Object of inputs provided by the user, as in js-sdk
|
2017-02-24 14:41:23 +03:00
|
|
|
* interactive-auth
|
2017-03-01 19:04:15 +03:00
|
|
|
* stageState: Stage-specific object used for communicating state information
|
2017-02-24 14:41:23 +03:00
|
|
|
* to the UI from the state-specific auth logic.
|
2017-03-01 19:04:15 +03:00
|
|
|
* Defined keys for stages are:
|
|
|
|
* m.login.email.identity:
|
|
|
|
* * emailSid: string representing the sid of the active
|
|
|
|
* verification session from the ID server, or
|
|
|
|
* null if no session is active.
|
|
|
|
* fail: a function which should be called with an error object if an
|
|
|
|
* error occurred during the auth stage. This will cause the auth
|
|
|
|
* session to be failed and the process to go back to the start.
|
|
|
|
* setEmailSid: m.login.email.identity only: a function to be called with the
|
|
|
|
* email sid after a token is requested.
|
|
|
|
* makeRegistrationUrl A function that makes a registration URL
|
2016-10-11 20:04:55 +03:00
|
|
|
*
|
|
|
|
* Each component may also provide the following functions (beyond the standard React ones):
|
|
|
|
* focus: set the input focus appropriately in the form.
|
|
|
|
*/
|
|
|
|
|
|
|
|
export const PasswordAuthEntry = React.createClass({
|
|
|
|
displayName: 'PasswordAuthEntry',
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
LOGIN_TYPE: "m.login.password",
|
|
|
|
},
|
|
|
|
|
|
|
|
propTypes: {
|
2017-03-02 17:36:54 +03:00
|
|
|
matrixClient: React.PropTypes.object.isRequired,
|
2016-10-11 20:04:55 +03:00
|
|
|
submitAuthDict: React.PropTypes.func.isRequired,
|
|
|
|
errorText: React.PropTypes.string,
|
2017-02-13 21:52:33 +03:00
|
|
|
// is the auth logic currently waiting for something to
|
|
|
|
// happen?
|
|
|
|
busy: React.PropTypes.bool,
|
2016-10-11 20:04:55 +03:00
|
|
|
},
|
|
|
|
|
2017-02-13 19:03:21 +03:00
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
2017-02-13 21:52:33 +03:00
|
|
|
passwordValid: false,
|
2017-02-13 19:03:21 +03:00
|
|
|
};
|
2016-10-11 20:04:55 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
focus: function() {
|
|
|
|
if (this.refs.passwordField) {
|
|
|
|
this.refs.passwordField.focus();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-02-13 21:52:33 +03:00
|
|
|
_onSubmit: function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
if (this.props.busy) return;
|
|
|
|
|
2016-10-11 20:04:55 +03:00
|
|
|
this.props.submitAuthDict({
|
|
|
|
type: PasswordAuthEntry.LOGIN_TYPE,
|
2017-02-24 14:41:23 +03:00
|
|
|
user: this.props.matrixClient.credentials.userId,
|
2016-10-11 20:04:55 +03:00
|
|
|
password: this.refs.passwordField.value,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:22:27 +03:00
|
|
|
_onPasswordFieldChange: function(ev) {
|
2016-10-11 20:04:55 +03:00
|
|
|
// enable the submit button iff the password is non-empty
|
2017-02-13 19:03:21 +03:00
|
|
|
this.setState({
|
2017-02-13 21:52:33 +03:00
|
|
|
passwordValid: Boolean(this.refs.passwordField.value),
|
2017-02-13 19:03:21 +03:00
|
|
|
});
|
2016-10-11 20:04:55 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
let passwordBoxClass = null;
|
|
|
|
|
|
|
|
if (this.props.errorText) {
|
|
|
|
passwordBoxClass = 'error';
|
|
|
|
}
|
|
|
|
|
2017-02-13 21:52:33 +03:00
|
|
|
let submitButtonOrSpinner;
|
|
|
|
if (this.props.busy) {
|
|
|
|
const Loader = sdk.getComponent("elements.Spinner");
|
|
|
|
submitButtonOrSpinner = <Loader />;
|
|
|
|
} else {
|
|
|
|
submitButtonOrSpinner = (
|
|
|
|
<input type="submit"
|
|
|
|
className="mx_Dialog_primary"
|
|
|
|
disabled={!this.state.passwordValid}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-11 20:04:55 +03:00
|
|
|
return (
|
|
|
|
<div>
|
2017-06-02 12:18:31 +03:00
|
|
|
<p>{_t("To continue, please enter your password.")}</p>
|
|
|
|
<p>{_t("Password:")}</p>
|
2017-02-13 19:03:21 +03:00
|
|
|
<form onSubmit={this._onSubmit}>
|
|
|
|
<input
|
|
|
|
ref="passwordField"
|
|
|
|
className={passwordBoxClass}
|
|
|
|
onChange={this._onPasswordFieldChange}
|
|
|
|
type="password"
|
|
|
|
/>
|
|
|
|
<div className="mx_button_row">
|
2017-02-13 21:52:33 +03:00
|
|
|
{submitButtonOrSpinner}
|
2017-02-13 19:03:21 +03:00
|
|
|
</div>
|
|
|
|
</form>
|
2016-10-11 20:04:55 +03:00
|
|
|
<div className="error">
|
|
|
|
{this.props.errorText}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
export const RecaptchaAuthEntry = React.createClass({
|
|
|
|
displayName: 'RecaptchaAuthEntry',
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
LOGIN_TYPE: "m.login.recaptcha",
|
|
|
|
},
|
|
|
|
|
|
|
|
propTypes: {
|
|
|
|
submitAuthDict: React.PropTypes.func.isRequired,
|
|
|
|
stageParams: React.PropTypes.object.isRequired,
|
|
|
|
errorText: React.PropTypes.string,
|
2017-03-21 21:40:41 +03:00
|
|
|
busy: React.PropTypes.bool,
|
2016-10-11 20:04:55 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
_onCaptchaResponse: function(response) {
|
|
|
|
this.props.submitAuthDict({
|
|
|
|
type: RecaptchaAuthEntry.LOGIN_TYPE,
|
|
|
|
response: response,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
2017-03-21 21:40:41 +03:00
|
|
|
if (this.props.busy) {
|
|
|
|
const Loader = sdk.getComponent("elements.Spinner");
|
|
|
|
return <Loader />;
|
|
|
|
}
|
|
|
|
|
2016-10-11 20:04:55 +03:00
|
|
|
const CaptchaForm = sdk.getComponent("views.login.CaptchaForm");
|
|
|
|
var sitePublicKey = this.props.stageParams.public_key;
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<CaptchaForm sitePublicKey={sitePublicKey}
|
|
|
|
onCaptchaResponse={this._onCaptchaResponse}
|
|
|
|
/>
|
|
|
|
<div className="error">
|
|
|
|
{this.props.errorText}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2017-02-24 14:41:23 +03:00
|
|
|
export const EmailIdentityAuthEntry = React.createClass({
|
|
|
|
displayName: 'EmailIdentityAuthEntry',
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
LOGIN_TYPE: "m.login.email.identity",
|
|
|
|
},
|
|
|
|
|
|
|
|
propTypes: {
|
2017-03-02 17:36:54 +03:00
|
|
|
matrixClient: React.PropTypes.object.isRequired,
|
2017-02-24 14:41:23 +03:00
|
|
|
submitAuthDict: React.PropTypes.func.isRequired,
|
|
|
|
authSessionId: React.PropTypes.string.isRequired,
|
2017-03-01 19:04:15 +03:00
|
|
|
clientSecret: React.PropTypes.string.isRequired,
|
2017-02-24 14:41:23 +03:00
|
|
|
inputs: React.PropTypes.object.isRequired,
|
2017-02-24 20:24:10 +03:00
|
|
|
stageState: React.PropTypes.object.isRequired,
|
2017-03-01 19:04:15 +03:00
|
|
|
fail: React.PropTypes.func.isRequired,
|
|
|
|
setEmailSid: React.PropTypes.func.isRequired,
|
|
|
|
makeRegistrationUrl: React.PropTypes.func.isRequired,
|
|
|
|
},
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
|
|
|
requestingToken: false,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillMount: function() {
|
|
|
|
if (this.props.stageState.emailSid === null) {
|
|
|
|
this.setState({requestingToken: true});
|
|
|
|
this._requestEmailToken().catch((e) => {
|
|
|
|
this.props.fail(e);
|
|
|
|
}).finally(() => {
|
|
|
|
this.setState({requestingToken: false});
|
|
|
|
}).done();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Requests a verification token by email.
|
|
|
|
*/
|
|
|
|
_requestEmailToken: function() {
|
|
|
|
const nextLink = this.props.makeRegistrationUrl({
|
|
|
|
client_secret: this.props.clientSecret,
|
|
|
|
hs_url: this.props.matrixClient.getHomeserverUrl(),
|
|
|
|
is_url: this.props.matrixClient.getIdentityServerUrl(),
|
|
|
|
session_id: this.props.authSessionId,
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.props.matrixClient.requestRegisterEmailToken(
|
|
|
|
this.props.inputs.emailAddress,
|
|
|
|
this.props.clientSecret,
|
|
|
|
1, // TODO: Multiple send attempts?
|
|
|
|
nextLink,
|
|
|
|
).then((result) => {
|
|
|
|
this.props.setEmailSid(result.sid);
|
|
|
|
});
|
2017-02-24 14:41:23 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
2017-03-01 19:04:15 +03:00
|
|
|
if (this.state.requestingToken) {
|
2017-02-24 14:41:23 +03:00
|
|
|
const Loader = sdk.getComponent("elements.Spinner");
|
|
|
|
return <Loader />;
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<div>
|
2017-05-30 17:09:57 +03:00
|
|
|
<p>{_t("An email has been sent to")} <i>{this.props.inputs.emailAddress}</i></p>
|
|
|
|
<p>{_t("Please check your email to continue registration.")}</p>
|
2017-02-24 14:41:23 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2017-03-14 14:50:13 +03:00
|
|
|
export const MsisdnAuthEntry = React.createClass({
|
|
|
|
displayName: 'MsisdnAuthEntry',
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
LOGIN_TYPE: "m.login.msisdn",
|
|
|
|
},
|
|
|
|
|
|
|
|
propTypes: {
|
|
|
|
inputs: React.PropTypes.shape({
|
|
|
|
phoneCountry: React.PropTypes.string,
|
|
|
|
phoneNumber: React.PropTypes.string,
|
|
|
|
}),
|
|
|
|
fail: React.PropTypes.func,
|
|
|
|
clientSecret: React.PropTypes.func,
|
|
|
|
submitAuthDict: React.PropTypes.func.isRequired,
|
|
|
|
matrixClient: React.PropTypes.object,
|
|
|
|
submitAuthDict: React.PropTypes.func,
|
|
|
|
},
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
|
|
|
token: '',
|
|
|
|
requestingToken: false,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillMount: function() {
|
|
|
|
this._sid = null;
|
|
|
|
this._msisdn = null;
|
|
|
|
this._tokenBox = null;
|
|
|
|
|
|
|
|
this.setState({requestingToken: true});
|
|
|
|
this._requestMsisdnToken().catch((e) => {
|
|
|
|
this.props.fail(e);
|
|
|
|
}).finally(() => {
|
|
|
|
this.setState({requestingToken: false});
|
|
|
|
}).done();
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Requests a verification token by SMS.
|
|
|
|
*/
|
|
|
|
_requestMsisdnToken: function() {
|
|
|
|
return this.props.matrixClient.requestRegisterMsisdnToken(
|
|
|
|
this.props.inputs.phoneCountry,
|
|
|
|
this.props.inputs.phoneNumber,
|
|
|
|
this.props.clientSecret,
|
|
|
|
1, // TODO: Multiple send attempts?
|
|
|
|
).then((result) => {
|
|
|
|
this._sid = result.sid;
|
|
|
|
this._msisdn = result.msisdn;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_onTokenChange: function(e) {
|
|
|
|
this.setState({
|
|
|
|
token: e.target.value,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_onFormSubmit: function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
if (this.state.token == '') return;
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
errorText: null,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.props.matrixClient.submitMsisdnToken(
|
|
|
|
this._sid, this.props.clientSecret, this.state.token
|
|
|
|
).then((result) => {
|
|
|
|
if (result.success) {
|
|
|
|
const idServerParsedUrl = url.parse(
|
|
|
|
this.props.matrixClient.getIdentityServerUrl(),
|
|
|
|
)
|
|
|
|
this.props.submitAuthDict({
|
|
|
|
type: MsisdnAuthEntry.LOGIN_TYPE,
|
|
|
|
threepid_creds: {
|
|
|
|
sid: this._sid,
|
|
|
|
client_secret: this.props.clientSecret,
|
|
|
|
id_server: idServerParsedUrl.host,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.setState({
|
2017-05-30 17:09:57 +03:00
|
|
|
errorText: _t("Token incorrect"),
|
2017-03-14 14:50:13 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}).catch((e) => {
|
|
|
|
this.props.fail(e);
|
|
|
|
console.log("Failed to submit msisdn token");
|
|
|
|
}).done();
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
if (this.state.requestingToken) {
|
|
|
|
const Loader = sdk.getComponent("elements.Spinner");
|
|
|
|
return <Loader />;
|
|
|
|
} else {
|
|
|
|
const enableSubmit = Boolean(this.state.token);
|
|
|
|
const submitClasses = classnames({
|
|
|
|
mx_InteractiveAuthEntryComponents_msisdnSubmit: true,
|
|
|
|
mx_UserSettings_button: true, // XXX button classes
|
|
|
|
});
|
|
|
|
return (
|
|
|
|
<div>
|
2017-05-30 17:09:57 +03:00
|
|
|
<p>{_t("A text message has been sent to")} +<i>{this._msisdn}</i></p>
|
|
|
|
<p>{_t("Please enter the code it contains:")}</p>
|
2017-03-14 14:50:13 +03:00
|
|
|
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
|
|
|
<form onSubmit={this._onFormSubmit}>
|
|
|
|
<input type="text"
|
|
|
|
className="mx_InteractiveAuthEntryComponents_msisdnEntry"
|
|
|
|
value={this.state.token}
|
|
|
|
onChange={this._onTokenChange}
|
|
|
|
/>
|
|
|
|
<br />
|
2017-06-02 12:18:31 +03:00
|
|
|
<input type="submit" value={_t("Submit")}
|
2017-03-14 14:50:13 +03:00
|
|
|
className={submitClasses}
|
|
|
|
disabled={!enableSubmit}
|
|
|
|
/>
|
|
|
|
</form>
|
|
|
|
<div className="error">
|
|
|
|
{this.state.errorText}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2016-10-11 20:04:55 +03:00
|
|
|
export const FallbackAuthEntry = React.createClass({
|
|
|
|
displayName: 'FallbackAuthEntry',
|
|
|
|
|
|
|
|
propTypes: {
|
2017-03-02 17:36:54 +03:00
|
|
|
matrixClient: React.PropTypes.object.isRequired,
|
2016-10-11 20:04:55 +03:00
|
|
|
authSessionId: React.PropTypes.string.isRequired,
|
|
|
|
loginType: React.PropTypes.string.isRequired,
|
|
|
|
submitAuthDict: React.PropTypes.func.isRequired,
|
|
|
|
errorText: React.PropTypes.string,
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillMount: function() {
|
|
|
|
// we have to make the user click a button, as browsers will block
|
|
|
|
// the popup if we open it immediately.
|
|
|
|
this._popupWindow = null;
|
|
|
|
window.addEventListener("message", this._onReceiveMessage);
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillUnmount: function() {
|
|
|
|
window.removeEventListener("message", this._onReceiveMessage);
|
|
|
|
if (this._popupWindow) {
|
|
|
|
this._popupWindow.close();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-02-13 22:09:43 +03:00
|
|
|
_onShowFallbackClick: function() {
|
2017-02-24 14:41:23 +03:00
|
|
|
var url = this.props.matrixClient.getFallbackAuthUrl(
|
2016-10-11 20:04:55 +03:00
|
|
|
this.props.loginType,
|
|
|
|
this.props.authSessionId
|
|
|
|
);
|
|
|
|
this._popupWindow = window.open(url);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onReceiveMessage: function(event) {
|
|
|
|
if (
|
|
|
|
event.data === "authDone" &&
|
2017-02-24 14:41:23 +03:00
|
|
|
event.origin === this.props.matrixClient.getHomeserverUrl()
|
2016-10-11 20:04:55 +03:00
|
|
|
) {
|
|
|
|
this.props.submitAuthDict({});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
<div>
|
2017-02-13 22:09:43 +03:00
|
|
|
<a onClick={this._onShowFallbackClick}>Start authentication</a>
|
2016-10-11 20:04:55 +03:00
|
|
|
<div className="error">
|
|
|
|
{this.props.errorText}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const AuthEntryComponents = [
|
|
|
|
PasswordAuthEntry,
|
|
|
|
RecaptchaAuthEntry,
|
2017-02-24 14:41:23 +03:00
|
|
|
EmailIdentityAuthEntry,
|
2017-03-14 14:50:13 +03:00
|
|
|
MsisdnAuthEntry,
|
2016-10-11 20:04:55 +03:00
|
|
|
];
|
|
|
|
|
|
|
|
export function getEntryComponentForLoginType(loginType) {
|
|
|
|
for (var c of AuthEntryComponents) {
|
|
|
|
if (c.LOGIN_TYPE == loginType) {
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return FallbackAuthEntry;
|
2017-01-20 17:22:27 +03:00
|
|
|
}
|