2015-11-30 18:11:04 +00:00
|
|
|
/*
|
2016-01-07 04:06:39 +00:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2017-03-14 11:50:13 +00:00
|
|
|
Copyright 2017 Vector Creations Ltd
|
2019-01-21 12:05:07 -06:00
|
|
|
Copyright 2018, 2019 New Vector Ltd
|
2015-11-30 18:11:04 +00: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.
|
|
|
|
|
*/
|
|
|
|
|
|
2017-04-21 11:37:08 +01:00
|
|
|
import React from 'react';
|
2019-08-30 10:27:51 +01:00
|
|
|
import createReactClass from 'create-react-class';
|
2017-12-26 14:03:18 +13:00
|
|
|
import PropTypes from 'prop-types';
|
2019-05-13 16:30:34 -06:00
|
|
|
import {_t, _td} from '../../../languageHandler';
|
2019-12-19 18:19:56 -07:00
|
|
|
import * as sdk from '../../../index';
|
2017-04-21 11:37:08 +01:00
|
|
|
import Login from '../../../Login';
|
2017-11-11 23:46:43 +00:00
|
|
|
import SdkConfig from '../../../SdkConfig';
|
2018-08-16 13:31:17 +01:00
|
|
|
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
2019-05-14 15:01:22 -06:00
|
|
|
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
2019-06-10 19:28:32 -06:00
|
|
|
import classNames from "classnames";
|
2019-12-12 19:31:52 -07:00
|
|
|
import AuthPage from "../../views/auth/AuthPage";
|
2020-03-02 14:59:54 +00:00
|
|
|
import SSOButton from "../../views/elements/SSOButton";
|
2020-03-04 11:41:16 -07:00
|
|
|
import PlatformPeg from '../../../PlatformPeg';
|
2015-11-30 18:11:04 +00:00
|
|
|
|
2017-05-02 18:30:46 +01:00
|
|
|
// For validating phone numbers without country codes
|
2018-06-21 14:04:08 +01:00
|
|
|
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
2017-05-02 18:30:46 +01:00
|
|
|
|
2019-01-28 14:58:21 -06:00
|
|
|
// Phases
|
2019-02-05 14:43:25 +00:00
|
|
|
// Show controls to configure server details
|
2019-02-06 16:30:53 +00:00
|
|
|
const PHASE_SERVER_DETAILS = 0;
|
|
|
|
|
// Show the appropriate login flow(s) for the server
|
|
|
|
|
const PHASE_LOGIN = 1;
|
2019-01-28 14:58:21 -06:00
|
|
|
|
2019-02-05 14:43:25 +00:00
|
|
|
// Enable phases for login
|
|
|
|
|
const PHASES_ENABLED = true;
|
2019-01-28 17:49:24 -06:00
|
|
|
|
2018-12-04 23:34:57 -07:00
|
|
|
// These are used in several places, and come from the js-sdk's autodiscovery
|
|
|
|
|
// stuff. We define them here so that they'll be picked up by i18n.
|
|
|
|
|
_td("Invalid homeserver discovery response");
|
2019-04-11 15:54:37 -06:00
|
|
|
_td("Failed to get autodiscovery configuration from server");
|
|
|
|
|
_td("Invalid base_url for m.homeserver");
|
|
|
|
|
_td("Homeserver URL does not appear to be a valid Matrix homeserver");
|
2018-12-04 23:34:57 -07:00
|
|
|
_td("Invalid identity server discovery response");
|
2019-04-11 15:54:37 -06:00
|
|
|
_td("Invalid base_url for m.identity_server");
|
|
|
|
|
_td("Identity server URL does not appear to be a valid identity server");
|
2018-12-04 23:34:57 -07:00
|
|
|
_td("General failure");
|
|
|
|
|
|
2015-11-30 18:11:04 +00:00
|
|
|
/**
|
2017-02-24 11:41:23 +00:00
|
|
|
* A wire component which glues together login UI components and Login logic
|
2015-11-30 18:11:04 +00:00
|
|
|
*/
|
2019-12-19 17:45:24 -07:00
|
|
|
export default createReactClass({
|
2016-08-11 13:05:16 +01:00
|
|
|
displayName: 'Login',
|
|
|
|
|
|
2015-11-30 18:11:04 +00:00
|
|
|
propTypes: {
|
2020-01-25 15:28:06 +00:00
|
|
|
// Called when the user has logged in. Params:
|
|
|
|
|
// - The object returned by the login API
|
|
|
|
|
// - The user's password, if applicable, (may be cached in memory for a
|
|
|
|
|
// short time so the user is not required to re-enter their password
|
|
|
|
|
// for operations like uploading cross-signing keys).
|
2017-12-26 14:03:18 +13:00
|
|
|
onLoggedIn: PropTypes.func.isRequired,
|
2016-03-06 14:33:36 -05:00
|
|
|
|
2019-05-02 23:07:40 -06:00
|
|
|
// If true, the component will consider itself busy.
|
|
|
|
|
busy: PropTypes.bool,
|
2019-02-20 13:40:30 +00:00
|
|
|
|
2016-05-27 14:57:43 +01:00
|
|
|
// Secondary HS which we try to log into if the user is using
|
|
|
|
|
// the default HS but login fails. Useful for migrating to a
|
2019-01-31 18:52:39 -06:00
|
|
|
// different homeserver without confusing users.
|
2017-12-26 14:03:18 +13:00
|
|
|
fallbackHsUrl: PropTypes.string,
|
2016-03-06 14:33:36 -05:00
|
|
|
|
2017-12-26 14:03:18 +13:00
|
|
|
defaultDeviceDisplayName: PropTypes.string,
|
2016-08-11 16:15:42 +01:00
|
|
|
|
2019-05-02 23:07:40 -06:00
|
|
|
// login shouldn't know or care how registration, password recovery,
|
|
|
|
|
// etc is done.
|
2017-12-26 14:03:18 +13:00
|
|
|
onRegisterClick: PropTypes.func.isRequired,
|
|
|
|
|
onForgotPasswordClick: PropTypes.func,
|
2018-02-07 15:51:03 +00:00
|
|
|
onServerConfigChange: PropTypes.func.isRequired,
|
2019-05-02 23:07:40 -06:00
|
|
|
|
|
|
|
|
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
2015-11-30 18:11:04 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {
|
|
|
|
|
busy: false,
|
2017-03-29 16:23:18 +01:00
|
|
|
errorText: null,
|
2016-08-03 17:49:29 +01:00
|
|
|
loginIncorrect: false,
|
2019-06-04 13:51:33 +01:00
|
|
|
canTryLogin: true, // can we attempt to log in or are there validation errors?
|
2019-01-25 10:42:45 -06:00
|
|
|
|
2017-03-14 11:50:13 +00:00
|
|
|
// used for preserving form values when changing homeserver
|
2016-03-06 14:33:36 -05:00
|
|
|
username: "",
|
2017-03-14 11:50:13 +00:00
|
|
|
phoneCountry: null,
|
|
|
|
|
phoneNumber: "",
|
2019-01-28 14:58:21 -06:00
|
|
|
|
|
|
|
|
// Phase of the overall login dialog.
|
2019-02-05 14:43:25 +00:00
|
|
|
phase: PHASE_LOGIN,
|
2019-01-28 14:58:21 -06:00
|
|
|
// The current login flow, such as password, SSO, etc.
|
2019-09-11 10:49:58 +01:00
|
|
|
currentFlow: null, // we need to load the flows from the server
|
2019-06-04 23:41:59 -06:00
|
|
|
|
|
|
|
|
// We perform liveliness checks later, but for now suppress the errors.
|
|
|
|
|
// We also track the server dead errors independently of the regular errors so
|
|
|
|
|
// that we can render it differently, and override any other error the user may
|
|
|
|
|
// be seeing.
|
|
|
|
|
serverIsAlive: true,
|
2019-06-10 19:28:32 -06:00
|
|
|
serverErrorIsFatal: false,
|
2019-06-04 23:41:59 -06:00
|
|
|
serverDeadError: "",
|
2015-11-30 18:11:04 +00:00
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
2020-04-01 11:05:49 -06:00
|
|
|
// TODO: [REACT-WARNING] Move this to constructor
|
|
|
|
|
UNSAFE_componentWillMount: function() {
|
2017-07-11 17:03:25 +01:00
|
|
|
this._unmounted = false;
|
2017-11-27 16:40:19 +00:00
|
|
|
|
|
|
|
|
// map from login step type to a function which will render a control
|
|
|
|
|
// letting you do that login type
|
|
|
|
|
this._stepRendererMap = {
|
|
|
|
|
'm.login.password': this._renderPasswordStep,
|
2018-11-15 19:05:39 +00:00
|
|
|
|
|
|
|
|
// CAS and SSO are the same thing, modulo the url we link to
|
2020-03-02 14:59:54 +00:00
|
|
|
'm.login.cas': () => this._renderSsoStep("cas"),
|
|
|
|
|
'm.login.sso': () => this._renderSsoStep("sso"),
|
2017-11-27 16:40:19 +00:00
|
|
|
};
|
|
|
|
|
|
2015-11-30 18:11:04 +00:00
|
|
|
this._initLoginLogic();
|
|
|
|
|
},
|
|
|
|
|
|
2017-07-11 17:03:25 +01:00
|
|
|
componentWillUnmount: function() {
|
|
|
|
|
this._unmounted = true;
|
|
|
|
|
},
|
|
|
|
|
|
2019-05-06 12:00:48 -06:00
|
|
|
componentWillReceiveProps(newProps) {
|
|
|
|
|
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
|
|
|
|
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
|
|
|
|
|
|
|
|
|
// Ensure that we end up actually logging in to the right place
|
|
|
|
|
this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
|
|
|
|
|
},
|
|
|
|
|
|
2018-06-23 17:12:00 +01:00
|
|
|
onPasswordLoginError: function(errorText) {
|
|
|
|
|
this.setState({
|
|
|
|
|
errorText,
|
|
|
|
|
loginIncorrect: Boolean(errorText),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2019-05-02 23:07:40 -06:00
|
|
|
isBusy: function() {
|
|
|
|
|
return this.state.busy || this.props.busy;
|
|
|
|
|
},
|
|
|
|
|
|
2019-07-14 23:23:48 -06:00
|
|
|
onPasswordLogin: async function(username, phoneCountry, phoneNumber, password) {
|
|
|
|
|
if (!this.state.serverIsAlive) {
|
|
|
|
|
this.setState({busy: true});
|
|
|
|
|
// Do a quick liveliness check on the URLs
|
2019-07-15 10:51:08 -06:00
|
|
|
let aliveAgain = true;
|
2019-07-14 23:23:48 -06:00
|
|
|
try {
|
|
|
|
|
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
|
|
|
|
|
this.props.serverConfig.hsUrl,
|
|
|
|
|
this.props.serverConfig.isUrl,
|
|
|
|
|
);
|
|
|
|
|
this.setState({serverIsAlive: true, errorText: ""});
|
|
|
|
|
} catch (e) {
|
2019-07-15 10:51:08 -06:00
|
|
|
const componentState = AutoDiscoveryUtils.authComponentStateForError(e);
|
2019-07-14 23:23:48 -06:00
|
|
|
this.setState({
|
|
|
|
|
busy: false,
|
2019-07-15 10:51:08 -06:00
|
|
|
...componentState,
|
2019-07-14 23:23:48 -06:00
|
|
|
});
|
2019-07-15 10:51:08 -06:00
|
|
|
aliveAgain = !componentState.serverErrorIsFatal;
|
2019-07-14 23:23:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prevent people from submitting their password when something isn't right.
|
2019-07-15 10:51:08 -06:00
|
|
|
if (!aliveAgain) {
|
2019-07-14 23:23:48 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-18 16:42:54 -06:00
|
|
|
|
2017-03-14 11:50:13 +00:00
|
|
|
this.setState({
|
2016-08-03 15:59:17 +01:00
|
|
|
busy: true,
|
|
|
|
|
errorText: null,
|
2016-08-03 17:49:29 +01:00
|
|
|
loginIncorrect: false,
|
2015-11-30 18:11:04 +00:00
|
|
|
});
|
|
|
|
|
|
2017-03-14 11:50:13 +00:00
|
|
|
this._loginLogic.loginViaPassword(
|
|
|
|
|
username, phoneCountry, phoneNumber, password,
|
|
|
|
|
).then((data) => {
|
2019-06-06 12:18:41 -06:00
|
|
|
this.setState({serverIsAlive: true}); // it must be, we logged in.
|
2020-01-25 15:28:06 +00:00
|
|
|
this.props.onLoggedIn(data, password);
|
2017-03-14 11:50:13 +00:00
|
|
|
}, (error) => {
|
2017-11-16 13:19:36 +00:00
|
|
|
if (this._unmounted) {
|
2017-07-11 17:03:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2017-06-08 15:49:48 +01:00
|
|
|
let errorText;
|
|
|
|
|
|
|
|
|
|
// Some error strings only apply for logging in
|
2017-06-08 16:08:19 +01:00
|
|
|
const usingEmail = username.indexOf("@") > 0;
|
2018-06-21 14:04:08 +01:00
|
|
|
if (error.httpStatus === 400 && usingEmail) {
|
2019-01-31 18:52:39 -06:00
|
|
|
errorText = _t('This homeserver does not support login using email address.');
|
2019-05-02 23:07:40 -06:00
|
|
|
} else if (error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
|
2018-08-16 13:31:17 +01:00
|
|
|
const errorTop = messageForResourceLimitError(
|
|
|
|
|
error.data.limit_type,
|
|
|
|
|
error.data.admin_contact, {
|
|
|
|
|
'monthly_active_user': _td(
|
|
|
|
|
"This homeserver has hit its Monthly Active User limit.",
|
|
|
|
|
),
|
|
|
|
|
'': _td(
|
|
|
|
|
"This homeserver has exceeded one of its resource limits.",
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
const errorDetail = messageForResourceLimitError(
|
|
|
|
|
error.data.limit_type,
|
|
|
|
|
error.data.admin_contact, {
|
|
|
|
|
'': _td(
|
|
|
|
|
"Please <a>contact your service administrator</a> to continue using this service.",
|
|
|
|
|
),
|
|
|
|
|
});
|
2018-08-02 14:25:17 +01:00
|
|
|
errorText = (
|
|
|
|
|
<div>
|
2018-08-16 13:31:17 +01:00
|
|
|
<div>{errorTop}</div>
|
|
|
|
|
<div className="mx_Login_smallError">{errorDetail}</div>
|
2018-08-02 14:25:17 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
2017-06-08 16:23:43 +01:00
|
|
|
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
|
2019-08-05 13:43:06 +02:00
|
|
|
if (error.errcode === 'M_USER_DEACTIVATED') {
|
|
|
|
|
errorText = _t('This account has been deactivated.');
|
|
|
|
|
} else if (SdkConfig.get()['disable_custom_urls']) {
|
2017-10-27 14:23:16 +01:00
|
|
|
errorText = (
|
|
|
|
|
<div>
|
2017-11-11 23:46:43 +00:00
|
|
|
<div>{ _t('Incorrect username and/or password.') }</div>
|
|
|
|
|
<div className="mx_Login_smallError">
|
2019-05-02 23:07:40 -06:00
|
|
|
{_t(
|
|
|
|
|
'Please note you are logging into the %(hs)s server, not matrix.org.',
|
|
|
|
|
{hs: this.props.serverConfig.hsName},
|
|
|
|
|
)}
|
2017-11-11 23:46:43 +00:00
|
|
|
</div>
|
2017-10-27 14:23:16 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2017-11-16 13:19:36 +00:00
|
|
|
errorText = _t('Incorrect username and/or password.');
|
2017-10-27 14:23:16 +01:00
|
|
|
}
|
2017-06-08 15:49:48 +01:00
|
|
|
} else {
|
|
|
|
|
// other errors, not specific to doing a password login
|
|
|
|
|
errorText = this._errorTextFromError(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({
|
2020-03-03 10:40:58 +00:00
|
|
|
busy: false,
|
2017-06-08 15:49:48 +01:00
|
|
|
errorText: errorText,
|
2017-06-08 16:23:43 +01:00
|
|
|
// 401 would be the sensible status code for 'incorrect password'
|
|
|
|
|
// but the login API gives a 403 https://matrix.org/jira/browse/SYN-744
|
|
|
|
|
// mentions this (although the bug is for UI auth which is not this)
|
|
|
|
|
// We treat both as an incorrect password
|
2018-06-21 14:04:08 +01:00
|
|
|
loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403,
|
2017-06-08 15:49:48 +01:00
|
|
|
});
|
2019-11-18 10:03:05 +00:00
|
|
|
});
|
2016-08-11 13:05:16 +01:00
|
|
|
},
|
|
|
|
|
|
2018-11-21 14:13:56 -07:00
|
|
|
onUsernameChanged: function(username) {
|
2016-03-06 14:33:36 -05:00
|
|
|
this.setState({ username: username });
|
2018-11-21 14:13:56 -07:00
|
|
|
},
|
|
|
|
|
|
2019-05-14 15:01:22 -06:00
|
|
|
onUsernameBlur: async function(username) {
|
2019-05-20 20:00:11 -06:00
|
|
|
const doWellknownLookup = username[0] === "@";
|
2019-01-29 23:50:31 -06:00
|
|
|
this.setState({
|
|
|
|
|
username: username,
|
2019-06-11 15:41:47 +01:00
|
|
|
busy: doWellknownLookup,
|
2019-05-02 23:07:40 -06:00
|
|
|
errorText: null,
|
2019-06-06 12:18:41 -06:00
|
|
|
canTryLogin: true,
|
2019-01-29 23:50:31 -06:00
|
|
|
});
|
2019-05-20 20:00:11 -06:00
|
|
|
if (doWellknownLookup) {
|
2018-10-18 16:42:54 -06:00
|
|
|
const serverName = username.split(':').slice(1).join(':');
|
2018-11-21 14:13:56 -07:00
|
|
|
try {
|
2019-05-14 15:01:22 -06:00
|
|
|
const result = await AutoDiscoveryUtils.validateServerName(serverName);
|
|
|
|
|
this.props.onServerConfigChange(result);
|
2019-06-11 15:41:47 +01:00
|
|
|
// We'd like to rely on new props coming in via `onServerConfigChange`
|
|
|
|
|
// so that we know the servers have definitely updated before clearing
|
|
|
|
|
// the busy state. In the case of a full MXID that resolves to the same
|
|
|
|
|
// HS as Riot's default HS though, there may not be any server change.
|
|
|
|
|
// To avoid this trap, we clear busy here. For cases where the server
|
|
|
|
|
// actually has changed, `_initLoginLogic` will be called and manages
|
|
|
|
|
// busy state for its own liveness check.
|
|
|
|
|
this.setState({
|
|
|
|
|
busy: false,
|
|
|
|
|
});
|
2018-11-21 14:13:56 -07:00
|
|
|
} catch (e) {
|
2018-11-27 13:41:34 -07:00
|
|
|
console.error("Problem parsing URL or unhandled error doing .well-known discovery:", e);
|
2019-05-14 15:01:22 -06:00
|
|
|
|
|
|
|
|
let message = _t("Failed to perform homeserver discovery");
|
|
|
|
|
if (e.translatedMessage) {
|
|
|
|
|
message = e.translatedMessage;
|
|
|
|
|
}
|
2019-06-04 23:41:59 -06:00
|
|
|
|
|
|
|
|
let errorText = message;
|
|
|
|
|
let discoveryState = {};
|
|
|
|
|
if (AutoDiscoveryUtils.isLivelinessError(e)) {
|
|
|
|
|
errorText = this.state.errorText;
|
2019-06-06 12:18:41 -06:00
|
|
|
discoveryState = AutoDiscoveryUtils.authComponentStateForError(e);
|
2019-06-04 23:41:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
busy: false,
|
|
|
|
|
errorText,
|
|
|
|
|
...discoveryState,
|
|
|
|
|
});
|
2018-11-21 14:13:56 -07:00
|
|
|
}
|
2018-10-18 16:42:54 -06:00
|
|
|
}
|
2016-03-06 14:33:36 -05:00
|
|
|
},
|
|
|
|
|
|
2017-03-14 11:50:13 +00:00
|
|
|
onPhoneCountryChanged: function(phoneCountry) {
|
|
|
|
|
this.setState({ phoneCountry: phoneCountry });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onPhoneNumberChanged: function(phoneNumber) {
|
2017-05-02 18:30:46 +01:00
|
|
|
this.setState({
|
|
|
|
|
phoneNumber: phoneNumber,
|
2019-01-30 10:50:51 -06:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onPhoneNumberBlur: function(phoneNumber) {
|
|
|
|
|
// Validate the phone number entered
|
|
|
|
|
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
|
|
|
|
|
this.setState({
|
|
|
|
|
errorText: _t('The phone number entered looks invalid'),
|
2019-06-04 13:51:33 +01:00
|
|
|
canTryLogin: false,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.setState({
|
|
|
|
|
errorText: null,
|
2019-06-06 12:18:41 -06:00
|
|
|
canTryLogin: true,
|
2019-01-30 10:50:51 -06:00
|
|
|
});
|
|
|
|
|
}
|
2017-03-14 11:50:13 +00:00
|
|
|
},
|
|
|
|
|
|
2018-12-20 17:26:13 -07:00
|
|
|
onRegisterClick: function(ev) {
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
this.props.onRegisterClick();
|
|
|
|
|
},
|
|
|
|
|
|
2020-03-03 23:23:53 -07:00
|
|
|
onTryRegisterClick: function(ev) {
|
2020-03-04 12:26:08 -07:00
|
|
|
const step = this._getCurrentFlowStep();
|
|
|
|
|
if (step === 'm.login.sso' || step === 'm.login.cas') {
|
2020-03-03 23:23:53 -07:00
|
|
|
// If we're showing SSO it means that registration is also probably disabled,
|
|
|
|
|
// so intercept the click and instead pretend the user clicked 'Sign in with SSO'.
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
2020-03-04 12:26:08 -07:00
|
|
|
const ssoKind = step === 'm.login.sso' ? 'sso' : 'cas';
|
2020-03-04 11:41:16 -07:00
|
|
|
PlatformPeg.get().startSingleSignOn(this._loginLogic.createTemporaryClient(), ssoKind);
|
2020-03-03 23:23:53 -07:00
|
|
|
} else {
|
|
|
|
|
// Don't intercept - just go through to the register page
|
|
|
|
|
this.onRegisterClick(ev);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2019-05-13 16:30:34 -06:00
|
|
|
async onServerDetailsNextPhaseClick() {
|
2019-01-28 14:58:21 -06:00
|
|
|
this.setState({
|
|
|
|
|
phase: PHASE_LOGIN,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2019-01-28 16:13:19 -06:00
|
|
|
onEditServerDetailsClick(ev) {
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
this.setState({
|
|
|
|
|
phase: PHASE_SERVER_DETAILS,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2019-06-04 23:41:59 -06:00
|
|
|
_initLoginLogic: async function(hsUrl, isUrl) {
|
2019-05-02 23:07:40 -06:00
|
|
|
hsUrl = hsUrl || this.props.serverConfig.hsUrl;
|
|
|
|
|
isUrl = isUrl || this.props.serverConfig.isUrl;
|
2015-11-30 18:11:04 +00:00
|
|
|
|
2019-06-04 23:41:59 -06:00
|
|
|
let isDefaultServer = false;
|
|
|
|
|
if (this.props.serverConfig.isDefault
|
|
|
|
|
&& hsUrl === this.props.serverConfig.hsUrl
|
|
|
|
|
&& isUrl === this.props.serverConfig.isUrl) {
|
|
|
|
|
isDefaultServer = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fallbackHsUrl = isDefaultServer ? this.props.fallbackHsUrl : null;
|
2016-05-27 14:57:43 +01:00
|
|
|
|
2017-10-11 17:56:17 +01:00
|
|
|
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
|
2016-08-11 16:15:42 +01:00
|
|
|
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
|
|
|
|
});
|
2015-11-30 18:11:04 +00:00
|
|
|
this._loginLogic = loginLogic;
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
busy: true,
|
2019-09-11 10:49:58 +01:00
|
|
|
currentFlow: null, // reset flow
|
2016-08-03 17:49:29 +01:00
|
|
|
loginIncorrect: false,
|
2015-11-30 18:11:04 +00:00
|
|
|
});
|
2017-04-21 11:37:08 +01:00
|
|
|
|
2019-06-04 23:41:59 -06:00
|
|
|
// Do a quick liveliness check on the URLs
|
|
|
|
|
try {
|
2019-11-01 10:44:30 +00:00
|
|
|
const { warning } =
|
|
|
|
|
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
|
|
|
|
|
if (warning) {
|
|
|
|
|
this.setState({
|
|
|
|
|
...AutoDiscoveryUtils.authComponentStateForError(warning),
|
|
|
|
|
errorText: "",
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.setState({
|
|
|
|
|
serverIsAlive: true,
|
|
|
|
|
errorText: "",
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-06-04 23:41:59 -06:00
|
|
|
} catch (e) {
|
|
|
|
|
this.setState({
|
|
|
|
|
busy: false,
|
2019-06-06 12:18:41 -06:00
|
|
|
...AutoDiscoveryUtils.authComponentStateForError(e),
|
2019-06-04 23:41:59 -06:00
|
|
|
});
|
2019-06-10 19:28:32 -06:00
|
|
|
if (this.state.serverErrorIsFatal) {
|
2019-11-14 15:23:04 +00:00
|
|
|
// Server is dead: show server details prompt instead
|
|
|
|
|
this.setState({
|
|
|
|
|
phase: PHASE_SERVER_DETAILS,
|
|
|
|
|
});
|
|
|
|
|
return;
|
2019-06-10 19:28:32 -06:00
|
|
|
}
|
2019-06-04 23:41:59 -06:00
|
|
|
}
|
|
|
|
|
|
2017-11-27 16:40:19 +00:00
|
|
|
loginLogic.getFlows().then((flows) => {
|
|
|
|
|
// look for a flow where we understand all of the steps.
|
|
|
|
|
for (let i = 0; i < flows.length; i++ ) {
|
|
|
|
|
if (!this._isSupportedFlow(flows[i])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we just pick the first flow where we support all the
|
|
|
|
|
// steps. (we don't have a UI for multiple logins so let's skip
|
|
|
|
|
// that for now).
|
|
|
|
|
loginLogic.chooseFlow(i);
|
|
|
|
|
this.setState({
|
|
|
|
|
currentFlow: this._getCurrentFlowStep(),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// we got to the end of the list without finding a suitable
|
|
|
|
|
// flow.
|
|
|
|
|
this.setState({
|
|
|
|
|
errorText: _t(
|
|
|
|
|
"This homeserver doesn't offer any login flows which are " +
|
|
|
|
|
"supported by this client.",
|
|
|
|
|
),
|
2017-04-21 11:37:08 +01:00
|
|
|
});
|
2019-06-04 23:41:59 -06:00
|
|
|
}, (err) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
errorText: this._errorTextFromError(err),
|
2017-06-08 15:49:48 +01:00
|
|
|
loginIncorrect: false,
|
2019-06-04 13:51:33 +01:00
|
|
|
canTryLogin: false,
|
2017-06-08 15:49:48 +01:00
|
|
|
});
|
2019-06-04 23:41:59 -06:00
|
|
|
}).finally(() => {
|
|
|
|
|
this.setState({
|
2017-04-21 11:37:08 +01:00
|
|
|
busy: false,
|
|
|
|
|
});
|
2019-11-18 10:03:05 +00:00
|
|
|
});
|
2015-11-30 18:11:04 +00:00
|
|
|
},
|
|
|
|
|
|
2017-11-27 16:40:19 +00:00
|
|
|
_isSupportedFlow: function(flow) {
|
|
|
|
|
// technically the flow can have multiple steps, but no one does this
|
|
|
|
|
// for login and loginLogic doesn't support it so we can ignore it.
|
|
|
|
|
if (!this._stepRendererMap[flow.type]) {
|
|
|
|
|
console.log("Skipping flow", flow, "due to unsupported login type", flow.type);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
2015-11-30 18:11:04 +00:00
|
|
|
_getCurrentFlowStep: function() {
|
2017-01-20 14:22:27 +00:00
|
|
|
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
|
2015-11-30 18:11:04 +00:00
|
|
|
},
|
|
|
|
|
|
2016-08-03 17:49:29 +01:00
|
|
|
_errorTextFromError(err) {
|
|
|
|
|
let errCode = err.errcode;
|
2015-11-30 18:11:04 +00:00
|
|
|
if (!errCode && err.httpStatus) {
|
|
|
|
|
errCode = "HTTP " + err.httpStatus;
|
|
|
|
|
}
|
2016-02-19 14:17:35 +00:00
|
|
|
|
2017-06-07 11:40:46 +01:00
|
|
|
let errorText = _t("Error: Problem communicating with the given homeserver.") +
|
|
|
|
|
(errCode ? " (" + errCode + ")" : "");
|
2016-02-19 14:17:35 +00:00
|
|
|
|
|
|
|
|
if (err.cors === 'rejected') {
|
|
|
|
|
if (window.location.protocol === 'https:' &&
|
2019-05-02 23:07:40 -06:00
|
|
|
(this.props.serverConfig.hsUrl.startsWith("http:") ||
|
|
|
|
|
!this.props.serverConfig.hsUrl.startsWith("http"))
|
2017-06-08 14:14:45 +01:00
|
|
|
) {
|
2016-02-19 14:17:35 +00:00
|
|
|
errorText = <span>
|
2018-06-21 14:04:08 +01:00
|
|
|
{ _t("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
|
|
|
|
"Either use HTTPS or <a>enable unsafe scripts</a>.", {},
|
|
|
|
|
{
|
|
|
|
|
'a': (sub) => {
|
2020-02-23 22:14:29 +00:00
|
|
|
return <a target="_blank" rel="noreferrer noopener"
|
2019-01-24 17:19:04 +00:00
|
|
|
href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
|
|
|
|
|
>
|
2018-06-21 14:04:08 +01:00
|
|
|
{ sub }
|
|
|
|
|
</a>;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-10-11 17:56:17 +01:00
|
|
|
) }
|
2016-02-19 14:17:35 +00:00
|
|
|
</span>;
|
2017-06-08 14:14:45 +01:00
|
|
|
} else {
|
2016-02-19 14:17:35 +00:00
|
|
|
errorText = <span>
|
2018-06-21 14:04:08 +01:00
|
|
|
{ _t("Can't connect to homeserver - please check your connectivity, ensure your " +
|
|
|
|
|
"<a>homeserver's SSL certificate</a> is trusted, and that a browser extension " +
|
|
|
|
|
"is not blocking requests.", {},
|
|
|
|
|
{
|
2020-02-23 22:19:18 +00:00
|
|
|
'a': (sub) =>
|
|
|
|
|
<a target="_blank" rel="noreferrer noopener" href={this.props.serverConfig.hsUrl}>
|
2019-05-02 23:07:40 -06:00
|
|
|
{ sub }
|
2020-02-23 22:19:18 +00:00
|
|
|
</a>,
|
2018-06-21 14:04:08 +01:00
|
|
|
},
|
2017-10-11 17:56:17 +01:00
|
|
|
) }
|
2016-02-19 14:17:35 +00:00
|
|
|
</span>;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 17:49:29 +01:00
|
|
|
return errorText;
|
2015-11-30 18:11:04 +00:00
|
|
|
},
|
|
|
|
|
|
2019-02-05 14:30:45 +00:00
|
|
|
renderServerComponent() {
|
2019-01-25 10:42:45 -06:00
|
|
|
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
|
|
|
|
|
|
|
|
|
if (SdkConfig.get()['disable_custom_urls']) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 17:49:24 -06:00
|
|
|
if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) {
|
2019-02-05 14:30:45 +00:00
|
|
|
return null;
|
2019-01-28 14:58:21 -06:00
|
|
|
}
|
|
|
|
|
|
2019-05-13 16:30:34 -06:00
|
|
|
const serverDetailsProps = {};
|
|
|
|
|
if (PHASES_ENABLED) {
|
|
|
|
|
serverDetailsProps.onAfterSubmit = this.onServerDetailsNextPhaseClick;
|
|
|
|
|
serverDetailsProps.submitText = _t("Next");
|
|
|
|
|
serverDetailsProps.submitClass = "mx_Login_submit";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <ServerConfig
|
2019-05-02 23:07:40 -06:00
|
|
|
serverConfig={this.props.serverConfig}
|
|
|
|
|
onServerConfigChange={this.props.onServerConfigChange}
|
2019-02-05 14:30:45 +00:00
|
|
|
delayTimeMs={250}
|
2019-05-13 16:30:34 -06:00
|
|
|
{...serverDetailsProps}
|
2019-02-05 14:30:45 +00:00
|
|
|
/>;
|
2019-01-25 10:42:45 -06:00
|
|
|
},
|
|
|
|
|
|
2019-01-28 22:19:33 -06:00
|
|
|
renderLoginComponentForStep() {
|
2019-01-28 17:49:24 -06:00
|
|
|
if (PHASES_ENABLED && this.state.phase !== PHASE_LOGIN) {
|
2019-01-28 14:58:21 -06:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 10:42:45 -06:00
|
|
|
const step = this.state.currentFlow;
|
|
|
|
|
|
2017-11-27 16:40:19 +00:00
|
|
|
if (!step) {
|
|
|
|
|
return null;
|
2015-11-30 18:11:04 +00:00
|
|
|
}
|
2017-11-27 16:40:19 +00:00
|
|
|
|
|
|
|
|
const stepRenderer = this._stepRendererMap[step];
|
|
|
|
|
|
|
|
|
|
if (stepRenderer) {
|
|
|
|
|
return stepRenderer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_renderPasswordStep: function() {
|
2019-01-21 16:11:10 -06:00
|
|
|
const PasswordLogin = sdk.getComponent('auth.PasswordLogin');
|
2019-02-20 13:40:30 +00:00
|
|
|
|
2019-01-28 16:13:19 -06:00
|
|
|
let onEditServerDetailsClick = null;
|
2019-02-05 14:30:45 +00:00
|
|
|
// If custom URLs are allowed, wire up the server details edit link.
|
|
|
|
|
if (PHASES_ENABLED && !SdkConfig.get()['disable_custom_urls']) {
|
2019-01-28 16:13:19 -06:00
|
|
|
onEditServerDetailsClick = this.onEditServerDetailsClick;
|
|
|
|
|
}
|
2019-02-20 13:40:30 +00:00
|
|
|
|
2017-11-27 16:40:19 +00:00
|
|
|
return (
|
|
|
|
|
<PasswordLogin
|
|
|
|
|
onSubmit={this.onPasswordLogin}
|
2018-06-23 17:12:00 +01:00
|
|
|
onError={this.onPasswordLoginError}
|
2019-01-28 16:13:19 -06:00
|
|
|
onEditServerDetailsClick={onEditServerDetailsClick}
|
2017-11-27 16:40:19 +00:00
|
|
|
initialUsername={this.state.username}
|
|
|
|
|
initialPhoneCountry={this.state.phoneCountry}
|
|
|
|
|
initialPhoneNumber={this.state.phoneNumber}
|
|
|
|
|
onUsernameChanged={this.onUsernameChanged}
|
2018-11-21 14:13:56 -07:00
|
|
|
onUsernameBlur={this.onUsernameBlur}
|
2017-11-27 16:40:19 +00:00
|
|
|
onPhoneCountryChanged={this.onPhoneCountryChanged}
|
|
|
|
|
onPhoneNumberChanged={this.onPhoneNumberChanged}
|
2019-01-30 10:50:51 -06:00
|
|
|
onPhoneNumberBlur={this.onPhoneNumberBlur}
|
2017-11-27 16:40:19 +00:00
|
|
|
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
|
|
|
|
loginIncorrect={this.state.loginIncorrect}
|
2019-05-02 23:07:40 -06:00
|
|
|
serverConfig={this.props.serverConfig}
|
2019-06-06 12:18:41 -06:00
|
|
|
disableSubmit={this.isBusy()}
|
2019-05-02 23:07:40 -06:00
|
|
|
/>
|
2017-11-27 16:40:19 +00:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
2020-03-02 14:59:54 +00:00
|
|
|
_renderSsoStep: function(loginType) {
|
2019-09-11 12:20:36 +01:00
|
|
|
const SignInToText = sdk.getComponent('views.auth.SignInToText');
|
2019-09-11 23:02:52 +01:00
|
|
|
|
|
|
|
|
let onEditServerDetailsClick = null;
|
|
|
|
|
// If custom URLs are allowed, wire up the server details edit link.
|
|
|
|
|
if (PHASES_ENABLED && !SdkConfig.get()['disable_custom_urls']) {
|
|
|
|
|
onEditServerDetailsClick = this.onEditServerDetailsClick;
|
|
|
|
|
}
|
2019-01-24 16:19:18 +00:00
|
|
|
// XXX: This link does *not* have a target="_blank" because single sign-on relies on
|
|
|
|
|
// redirecting the user back to a URI once they're logged in. On the web, this means
|
|
|
|
|
// we use the same window and redirect back to riot. On electron, this actually
|
|
|
|
|
// opens the SSO page in the electron app itself due to
|
|
|
|
|
// https://github.com/electron/electron/issues/8841 and so happens to work.
|
|
|
|
|
// If this bug gets fixed, it will break SSO since it will open the SSO page in the
|
|
|
|
|
// user's browser, let them log into their SSO provider, then redirect their browser
|
|
|
|
|
// to vector://vector which, of course, will not work.
|
2019-09-11 17:34:29 +01:00
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<SignInToText serverConfig={this.props.serverConfig}
|
2019-09-11 23:02:52 +01:00
|
|
|
onEditServerDetailsClick={onEditServerDetailsClick} />
|
2019-09-11 12:20:36 +01:00
|
|
|
|
2020-03-02 14:59:54 +00:00
|
|
|
<SSOButton
|
|
|
|
|
className="mx_Login_sso_link mx_Login_submit"
|
|
|
|
|
matrixClient={this._loginLogic.createTemporaryClient()}
|
|
|
|
|
loginType={loginType} />
|
2019-09-11 17:34:29 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
2015-11-30 18:11:04 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
2017-04-21 11:37:08 +01:00
|
|
|
const Loader = sdk.getComponent("elements.Spinner");
|
2019-01-21 18:33:17 -06:00
|
|
|
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
2019-01-22 19:28:23 -06:00
|
|
|
const AuthBody = sdk.getComponent("auth.AuthBody");
|
2019-05-02 23:07:40 -06:00
|
|
|
const loader = this.isBusy() ? <div className="mx_Login_loader"><Loader /></div> : null;
|
2015-11-30 18:11:04 +00:00
|
|
|
|
2019-06-04 13:30:36 +01:00
|
|
|
const errorText = this.state.errorText;
|
2018-10-18 16:42:54 -06:00
|
|
|
|
2017-10-25 02:18:14 +01:00
|
|
|
let errorTextSection;
|
2018-10-18 16:42:54 -06:00
|
|
|
if (errorText) {
|
2017-10-25 02:18:14 +01:00
|
|
|
errorTextSection = (
|
|
|
|
|
<div className="mx_Login_error">
|
2018-10-18 16:42:54 -06:00
|
|
|
{ errorText }
|
2017-10-25 02:18:14 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-03-15 21:04:11 +00:00
|
|
|
|
2019-06-04 23:41:59 -06:00
|
|
|
let serverDeadSection;
|
|
|
|
|
if (!this.state.serverIsAlive) {
|
2019-06-10 19:28:32 -06:00
|
|
|
const classes = classNames({
|
|
|
|
|
"mx_Login_error": true,
|
|
|
|
|
"mx_Login_serverError": true,
|
|
|
|
|
"mx_Login_serverErrorNonFatal": !this.state.serverErrorIsFatal,
|
|
|
|
|
});
|
2019-06-04 23:41:59 -06:00
|
|
|
serverDeadSection = (
|
2019-06-10 19:28:32 -06:00
|
|
|
<div className={classes}>
|
2019-06-04 23:41:59 -06:00
|
|
|
{this.state.serverDeadError}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-30 18:11:04 +00:00
|
|
|
return (
|
2019-01-21 18:33:17 -06:00
|
|
|
<AuthPage>
|
2019-01-21 18:49:28 -06:00
|
|
|
<AuthHeader />
|
2019-01-22 19:28:23 -06:00
|
|
|
<AuthBody>
|
2019-01-23 12:10:25 -06:00
|
|
|
<h2>
|
2019-02-05 15:36:00 +00:00
|
|
|
{_t('Sign in')}
|
2019-01-23 12:10:25 -06:00
|
|
|
{loader}
|
|
|
|
|
</h2>
|
2019-01-21 18:49:28 -06:00
|
|
|
{ errorTextSection }
|
2019-06-04 23:41:59 -06:00
|
|
|
{ serverDeadSection }
|
2019-02-05 14:30:45 +00:00
|
|
|
{ this.renderServerComponent() }
|
2019-01-28 22:19:33 -06:00
|
|
|
{ this.renderLoginComponentForStep() }
|
2020-03-03 23:23:53 -07:00
|
|
|
<a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#">
|
2019-01-23 14:33:56 -06:00
|
|
|
{ _t('Create account') }
|
2019-01-21 18:49:28 -06:00
|
|
|
</a>
|
2019-01-22 19:28:23 -06:00
|
|
|
</AuthBody>
|
2019-01-21 18:33:17 -06:00
|
|
|
</AuthPage>
|
2015-11-30 18:11:04 +00:00
|
|
|
);
|
2017-10-11 17:56:17 +01:00
|
|
|
},
|
2015-11-30 18:11:04 +00:00
|
|
|
});
|