Merge pull request #3067 from matrix-org/travis/fail-fast-but-not-too-fast

Fail more softly on homeserver liveliness errors
This commit is contained in:
Travis Ralston 2019-06-07 07:43:45 -06:00 committed by GitHub
commit 795a273e26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 272 additions and 41 deletions

View file

@ -12,7 +12,7 @@
right: 0; right: 0;
margin: auto; margin: auto;
width: 500px; width: 500px;
height: 200px; height: 125px;
border: 1px solid #f22; border: 1px solid #f22;
padding: 10px 10px 20px; padding: 10px 10px 20px;
background-color: #fcc; background-color: #fcc;

View file

@ -62,6 +62,11 @@ limitations under the License.
margin-bottom: 12px; margin-bottom: 12px;
} }
.mx_Login_error.mx_Login_serverError {
text-align: left;
font-weight: normal;
}
.mx_Login_type_container { .mx_Login_type_container {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -16,22 +16,18 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t} from "../../languageHandler";
export default class GenericErrorPage extends React.PureComponent { export default class GenericErrorPage extends React.PureComponent {
static propTypes = { static propTypes = {
title: PropTypes.object.isRequired, // jsx for title
message: PropTypes.object.isRequired, // jsx to display message: PropTypes.object.isRequired, // jsx to display
}; };
render() { render() {
return <div className='mx_GenericErrorPage'> return <div className='mx_GenericErrorPage'>
<div className='mx_GenericErrorPage_box'> <div className='mx_GenericErrorPage_box'>
<h1>{_t("Error loading Riot")}</h1> <h1>{this.props.title}</h1>
<p>{this.props.message}</p> <p>{this.props.message}</p>
<p>{_t(
"If this is unexpected, please contact your system administrator " +
"or technical support representative.",
)}</p>
</div> </div>
</div>; </div>;
} }

View file

@ -22,7 +22,7 @@ import sdk from '../../../index';
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import PasswordReset from "../../../PasswordReset"; import PasswordReset from "../../../PasswordReset";
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
// Phases // Phases
// Show controls to configure server details // Show controls to configure server details
@ -53,9 +53,40 @@ module.exports = React.createClass({
password: "", password: "",
password2: "", password2: "",
errorText: null, errorText: null,
// 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,
serverDeadError: "",
}; };
}, },
componentWillMount: function() {
this._checkServerLiveliness(this.props.serverConfig);
},
componentWillReceiveProps: function(newProps) {
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
// Do a liveliness check on the new URLs
this._checkServerLiveliness(newProps.serverConfig);
},
_checkServerLiveliness: async function(serverConfig) {
try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
serverConfig.hsUrl,
serverConfig.isUrl,
);
this.setState({serverIsAlive: true});
} catch (e) {
this.setState(AutoDiscoveryUtils.authComponentStateForError(e));
}
},
submitPasswordReset: function(email, password) { submitPasswordReset: function(email, password) {
this.setState({ this.setState({
phase: PHASE_SENDING_EMAIL, phase: PHASE_SENDING_EMAIL,
@ -86,9 +117,11 @@ module.exports = React.createClass({
}); });
}, },
onSubmitForm: function(ev) { onSubmitForm: async function(ev) {
ev.preventDefault(); ev.preventDefault();
await this._checkServerLiveliness(this.props.serverConfig);
if (!this.state.email) { if (!this.state.email) {
this.showErrorDialog(_t('The email address linked to your account must be entered.')); this.showErrorDialog(_t('The email address linked to your account must be entered.'));
} else if (!this.state.password || !this.state.password2) { } else if (!this.state.password || !this.state.password2) {
@ -173,11 +206,20 @@ module.exports = React.createClass({
const Field = sdk.getComponent('elements.Field'); const Field = sdk.getComponent('elements.Field');
let errorText = null; let errorText = null;
const err = this.state.errorText || this.props.defaultServerDiscoveryError; const err = this.state.errorText;
if (err) { if (err) {
errorText = <div className="mx_Login_error">{ err }</div>; errorText = <div className="mx_Login_error">{ err }</div>;
} }
let serverDeadSection;
if (!this.state.serverIsAlive) {
serverDeadSection = (
<div className="mx_Login_error mx_Login_serverError">
{this.state.serverDeadError}
</div>
);
}
let yourMatrixAccountText = _t('Your Matrix account on %(serverName)s', { let yourMatrixAccountText = _t('Your Matrix account on %(serverName)s', {
serverName: this.props.serverConfig.hsName, serverName: this.props.serverConfig.hsName,
}); });
@ -207,11 +249,12 @@ module.exports = React.createClass({
} }
return <div> return <div>
{errorText}
{serverDeadSection}
<h3> <h3>
{yourMatrixAccountText} {yourMatrixAccountText}
{editLink} {editLink}
</h3> </h3>
{errorText}
<form onSubmit={this.onSubmitForm}> <form onSubmit={this.onSubmitForm}>
<div className="mx_AuthBody_fieldRow"> <div className="mx_AuthBody_fieldRow">
<Field <Field
@ -246,7 +289,11 @@ module.exports = React.createClass({
'A verification email will be sent to your inbox to confirm ' + 'A verification email will be sent to your inbox to confirm ' +
'setting your new password.', 'setting your new password.',
)}</span> )}</span>
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} /> <input
className="mx_Login_submit"
type="submit"
value={_t('Send Reset Email')}
/>
</form> </form>
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#"> <a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
{_t('Sign in instead')} {_t('Sign in instead')}

View file

@ -94,6 +94,13 @@ module.exports = React.createClass({
phase: PHASE_LOGIN, phase: PHASE_LOGIN,
// The current login flow, such as password, SSO, etc. // The current login flow, such as password, SSO, etc.
currentFlow: "m.login.password", currentFlow: "m.login.password",
// 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,
serverDeadError: "",
}; };
}, },
@ -138,7 +145,7 @@ module.exports = React.createClass({
onPasswordLogin: function(username, phoneCountry, phoneNumber, password) { onPasswordLogin: function(username, phoneCountry, phoneNumber, password) {
// Prevent people from submitting their password when something isn't right. // Prevent people from submitting their password when something isn't right.
if (this.isBusy() || !this.state.canTryLogin) return; if (this.isBusy()) return;
this.setState({ this.setState({
busy: true, busy: true,
@ -149,6 +156,7 @@ module.exports = React.createClass({
this._loginLogic.loginViaPassword( this._loginLogic.loginViaPassword(
username, phoneCountry, phoneNumber, password, username, phoneCountry, phoneNumber, password,
).then((data) => { ).then((data) => {
this.setState({serverIsAlive: true}); // it must be, we logged in.
this.props.onLoggedIn(data); this.props.onLoggedIn(data);
}, (error) => { }, (error) => {
if (this._unmounted) { if (this._unmounted) {
@ -247,7 +255,19 @@ module.exports = React.createClass({
if (e.translatedMessage) { if (e.translatedMessage) {
message = e.translatedMessage; message = e.translatedMessage;
} }
this.setState({errorText: message, busy: false, canTryLogin: false});
let errorText = message;
let discoveryState = {};
if (AutoDiscoveryUtils.isLivelinessError(e)) {
errorText = this.state.errorText;
discoveryState = AutoDiscoveryUtils.authComponentStateForError(e);
}
this.setState({
busy: false,
errorText,
...discoveryState,
});
} }
} }
}, },
@ -297,13 +317,18 @@ module.exports = React.createClass({
}); });
}, },
_initLoginLogic: function(hsUrl, isUrl) { _initLoginLogic: async function(hsUrl, isUrl) {
const self = this;
hsUrl = hsUrl || this.props.serverConfig.hsUrl; hsUrl = hsUrl || this.props.serverConfig.hsUrl;
isUrl = isUrl || this.props.serverConfig.isUrl; isUrl = isUrl || this.props.serverConfig.isUrl;
// TODO: TravisR - Only use this if the homeserver is the default homeserver let isDefaultServer = false;
const fallbackHsUrl = this.props.fallbackHsUrl; if (this.props.serverConfig.isDefault
&& hsUrl === this.props.serverConfig.hsUrl
&& isUrl === this.props.serverConfig.isUrl) {
isDefaultServer = true;
}
const fallbackHsUrl = isDefaultServer ? this.props.fallbackHsUrl : null;
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, { const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
@ -315,6 +340,18 @@ module.exports = React.createClass({
loginIncorrect: false, loginIncorrect: false,
}); });
// Do a quick liveliness check on the URLs
try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
this.setState({serverIsAlive: true, errorText: ""});
} catch (e) {
this.setState({
busy: false,
...AutoDiscoveryUtils.authComponentStateForError(e),
});
return; // Server is dead - do not continue.
}
loginLogic.getFlows().then((flows) => { loginLogic.getFlows().then((flows) => {
// look for a flow where we understand all of the steps. // look for a flow where we understand all of the steps.
for (let i = 0; i < flows.length; i++ ) { for (let i = 0; i < flows.length; i++ ) {
@ -339,14 +376,14 @@ module.exports = React.createClass({
"supported by this client.", "supported by this client.",
), ),
}); });
}, function(err) { }, (err) => {
self.setState({ this.setState({
errorText: self._errorTextFromError(err), errorText: this._errorTextFromError(err),
loginIncorrect: false, loginIncorrect: false,
canTryLogin: false, canTryLogin: false,
}); });
}).finally(function() { }).finally(() => {
self.setState({ this.setState({
busy: false, busy: false,
}); });
}).done(); }).done();
@ -522,6 +559,15 @@ module.exports = React.createClass({
); );
} }
let serverDeadSection;
if (!this.state.serverIsAlive) {
serverDeadSection = (
<div className="mx_Login_error mx_Login_serverError">
{this.state.serverDeadError}
</div>
);
}
return ( return (
<AuthPage> <AuthPage>
<AuthHeader /> <AuthHeader />
@ -531,6 +577,7 @@ module.exports = React.createClass({
{loader} {loader}
</h2> </h2>
{ errorTextSection } { errorTextSection }
{ serverDeadSection }
{ this.renderServerComponent() } { this.renderServerComponent() }
{ this.renderLoginComponentForStep() } { this.renderLoginComponentForStep() }
<a className="mx_AuthBody_changeFlow" onClick={this.onRegisterClick} href="#"> <a className="mx_AuthBody_changeFlow" onClick={this.onRegisterClick} href="#">

View file

@ -26,7 +26,7 @@ import { _t, _td } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
import * as ServerType from '../../views/auth/ServerTypeSelector'; import * as ServerType from '../../views/auth/ServerTypeSelector';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
// Phases // Phases
// Show controls to configure server details // Show controls to configure server details
@ -79,6 +79,13 @@ module.exports = React.createClass({
// Phase of the overall registration dialog. // Phase of the overall registration dialog.
phase: PHASE_REGISTRATION, phase: PHASE_REGISTRATION,
flows: null, flows: null,
// 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,
serverDeadError: "",
}; };
}, },
@ -152,6 +159,19 @@ module.exports = React.createClass({
errorText: null, errorText: null,
}); });
if (!serverConfig) serverConfig = this.props.serverConfig; if (!serverConfig) serverConfig = this.props.serverConfig;
// Do a liveliness check on the URLs
try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
serverConfig.hsUrl,
serverConfig.isUrl,
);
this.setState({serverIsAlive: true});
} catch (e) {
this.setState(AutoDiscoveryUtils.authComponentStateForError(e));
return; // Server is dead - do not continue.
}
const {hsUrl, isUrl} = serverConfig; const {hsUrl, isUrl} = serverConfig;
this._matrixClient = Matrix.createClient({ this._matrixClient = Matrix.createClient({
baseUrl: hsUrl, baseUrl: hsUrl,
@ -447,6 +467,7 @@ module.exports = React.createClass({
onEditServerDetailsClick={onEditServerDetailsClick} onEditServerDetailsClick={onEditServerDetailsClick}
flows={this.state.flows} flows={this.state.flows}
serverConfig={this.props.serverConfig} serverConfig={this.props.serverConfig}
canSubmit={this.state.serverIsAlive}
/>; />;
} }
}, },
@ -462,6 +483,15 @@ module.exports = React.createClass({
errorText = <div className="mx_Login_error">{ err }</div>; errorText = <div className="mx_Login_error">{ err }</div>;
} }
let serverDeadSection;
if (!this.state.serverIsAlive) {
serverDeadSection = (
<div className="mx_Login_error mx_Login_serverError">
{this.state.serverDeadError}
</div>
);
}
const signIn = <a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#"> const signIn = <a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
{ _t('Sign in instead') } { _t('Sign in instead') }
</a>; </a>;
@ -480,6 +510,7 @@ module.exports = React.createClass({
<AuthBody> <AuthBody>
<h2>{ _t('Create your account') }</h2> <h2>{ _t('Create your account') }</h2>
{ errorText } { errorText }
{ serverDeadSection }
{ this.renderServerComponent() } { this.renderServerComponent() }
{ this.renderRegisterComponent() } { this.renderRegisterComponent() }
{ goBack } { goBack }

View file

@ -108,6 +108,8 @@ export default class ModularServerConfig extends React.PureComponent {
busy: false, busy: false,
errorText: message, errorText: message,
}); });
return null;
} }
} }
@ -132,7 +134,8 @@ export default class ModularServerConfig extends React.PureComponent {
onSubmit = async (ev) => { onSubmit = async (ev) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
await this.validateServer(); const result = await this.validateServer();
if (!result) return; // Do not continue.
if (this.props.onAfterSubmit) { if (this.props.onAfterSubmit) {
this.props.onAfterSubmit(); this.props.onAfterSubmit();

View file

@ -53,11 +53,13 @@ module.exports = React.createClass({
onEditServerDetailsClick: PropTypes.func, onEditServerDetailsClick: PropTypes.func,
flows: PropTypes.arrayOf(PropTypes.object).isRequired, flows: PropTypes.arrayOf(PropTypes.object).isRequired,
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
canSubmit: PropTypes.bool,
}, },
getDefaultProps: function() { getDefaultProps: function() {
return { return {
onValidationChange: console.error, onValidationChange: console.error,
canSubmit: true,
}; };
}, },
@ -80,6 +82,8 @@ module.exports = React.createClass({
onSubmit: async function(ev) { onSubmit: async function(ev) {
ev.preventDefault(); ev.preventDefault();
if (!this.props.canSubmit) return;
const allFieldsValid = await this.verifyFieldsBeforeSubmit(); const allFieldsValid = await this.verifyFieldsBeforeSubmit();
if (!allFieldsValid) { if (!allFieldsValid) {
return; return;
@ -540,7 +544,7 @@ module.exports = React.createClass({
} }
const registerButton = ( const registerButton = (
<input className="mx_Login_submit" type="submit" value={_t("Register")} /> <input className="mx_Login_submit" type="submit" value={_t("Register")} disabled={!this.props.canSubmit} />
); );
return ( return (

View file

@ -109,6 +109,8 @@ export default class ServerConfig extends React.PureComponent {
busy: false, busy: false,
errorText: message, errorText: message,
}); });
return null;
} }
} }
@ -137,7 +139,8 @@ export default class ServerConfig extends React.PureComponent {
onSubmit = async (ev) => { onSubmit = async (ev) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
await this.validateServer(); const result = await this.validateServer();
if (!result) return; // Do not continue.
if (this.props.onAfterSubmit) { if (this.props.onAfterSubmit) {
this.props.onAfterSubmit(); this.props.onAfterSubmit();

View file

@ -249,8 +249,13 @@
"%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …",
"%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …",
"%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …", "%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …",
"Cannot reach homeserver": "Cannot reach homeserver",
"Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin",
"Your Riot is misconfigured": "Your Riot is misconfigured",
"Ask your Riot admin to check <a>your config</a> for incorrect or duplicate entries.": "Ask your Riot admin to check <a>your config</a> for incorrect or duplicate entries.",
"No homeserver URL provided": "No homeserver URL provided", "No homeserver URL provided": "No homeserver URL provided",
"Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration",
"Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration",
"This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.",
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.", "Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
@ -304,7 +309,6 @@
"Custom user status messages": "Custom user status messages", "Custom user status messages": "Custom user status messages",
"Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)",
"Render simple counters in room header": "Render simple counters in room header", "Render simple counters in room header": "Render simple counters in room header",
"Custom Notification Sounds": "Custom Notification Sounds",
"Edit messages after they have been sent (refresh to apply changes)": "Edit messages after they have been sent (refresh to apply changes)", "Edit messages after they have been sent (refresh to apply changes)": "Edit messages after they have been sent (refresh to apply changes)",
"React to messages with emoji (refresh to apply changes)": "React to messages with emoji (refresh to apply changes)", "React to messages with emoji (refresh to apply changes)": "React to messages with emoji (refresh to apply changes)",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
@ -1385,8 +1389,6 @@
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality", "You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You must join the room to see its files": "You must join the room to see its files", "You must join the room to see its files": "You must join the room to see its files",
"There are no visible files in this room": "There are no visible files in this room", "There are no visible files in this room": "There are no visible files in this room",
"Error loading Riot": "Error loading Riot",
"If this is unexpected, please contact your system administrator or technical support representative.": "If this is unexpected, please contact your system administrator or technical support representative.",
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n", "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n",
"Add rooms to the community summary": "Add rooms to the community summary", "Add rooms to the community summary": "Add rooms to the community summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",

View file

@ -14,11 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react';
import {AutoDiscovery} from "matrix-js-sdk"; import {AutoDiscovery} from "matrix-js-sdk";
import {_td, newTranslatableError} from "../languageHandler"; import {_t, _td, newTranslatableError} from "../languageHandler";
import {makeType} from "./TypeUtils"; import {makeType} from "./TypeUtils";
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
const LIVLINESS_DISCOVERY_ERRORS = [
AutoDiscovery.ERROR_INVALID_HOMESERVER,
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
];
export class ValidatedServerConfig { export class ValidatedServerConfig {
hsUrl: string; hsUrl: string;
hsName: string; hsName: string;
@ -31,7 +37,65 @@ export class ValidatedServerConfig {
} }
export default class AutoDiscoveryUtils { export default class AutoDiscoveryUtils {
static async validateServerConfigWithStaticUrls(homeserverUrl: string, identityUrl: string): ValidatedServerConfig { /**
* Checks if a given error or error message is considered an error
* relating to the liveliness of the server. Must be an error returned
* from this AutoDiscoveryUtils class.
* @param {string|Error} error The error to check
* @returns {boolean} True if the error is a liveliness error.
*/
static isLivelinessError(error: string|Error): boolean {
if (!error) return false;
return !!LIVLINESS_DISCOVERY_ERRORS.find(e => e === error || e === error.message);
}
/**
* Gets the common state for auth components (login, registration, forgot
* password) for a given validation error.
* @param {Error} err The error encountered.
* @returns {{serverDeadError: (string|*), serverIsAlive: boolean}} The state
* for the component, given the error.
*/
static authComponentStateForError(err: Error): {serverIsAlive: boolean, serverDeadError: string} {
let title = _t("Cannot reach homeserver");
let body = _t("Ensure you have a stable internet connection, or get in touch with the server admin");
if (!AutoDiscoveryUtils.isLivelinessError(err)) {
title = _t("Your Riot is misconfigured");
body = _t(
"Ask your Riot admin to check <a>your config</a> for incorrect or duplicate entries.",
{}, {
a: (sub) => {
return <a
href="https://github.com/vector-im/riot-web#configjson"
target="_blank"
rel="noopener"
>{sub}</a>;
},
},
);
}
return {
serverIsAlive: false,
serverDeadError: (
<div>
<strong>{title}</strong>
<div>{body}</div>
</div>
),
};
}
/**
* Validates a server configuration, using a pair of URLs as input.
* @param {string} homeserverUrl The homeserver URL.
* @param {string} identityUrl The identity server URL.
* @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will
* not be raised.
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
*/
static async validateServerConfigWithStaticUrls(
homeserverUrl: string, identityUrl: string, syntaxOnly = false): ValidatedServerConfig {
if (!homeserverUrl) { if (!homeserverUrl) {
throw newTranslatableError(_td("No homeserver URL provided")); throw newTranslatableError(_td("No homeserver URL provided"));
} }
@ -50,15 +114,32 @@ export default class AutoDiscoveryUtils {
const url = new URL(homeserverUrl); const url = new URL(homeserverUrl);
const serverName = url.hostname; const serverName = url.hostname;
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result); return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result, syntaxOnly);
} }
static async validateServerName(serverName: string): ValidatedServerConfig { /**
* Validates a server configuration, using a homeserver domain name as input.
* @param {string} serverName The homeserver domain name (eg: "matrix.org") to validate.
* @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will
* not be raised.
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
*/
static async validateServerName(serverName: string, syntaxOnly=false): ValidatedServerConfig {
const result = await AutoDiscovery.findClientConfig(serverName); const result = await AutoDiscovery.findClientConfig(serverName);
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result); return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
} }
static buildValidatedConfigFromDiscovery(serverName: string, discoveryResult): ValidatedServerConfig { /**
* Validates a server configuration, using a pre-calculated AutoDiscovery result as
* input.
* @param {string} serverName The domain name the AutoDiscovery result is for.
* @param {*} discoveryResult The AutoDiscovery result.
* @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will
* not be raised.
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
*/
static buildValidatedConfigFromDiscovery(
serverName: string, discoveryResult, syntaxOnly=false): ValidatedServerConfig {
if (!discoveryResult || !discoveryResult["m.homeserver"]) { if (!discoveryResult || !discoveryResult["m.homeserver"]) {
// This shouldn't happen without major misconfiguration, so we'll log a bit of information // This shouldn't happen without major misconfiguration, so we'll log a bit of information
// in the log so we can find this bit of codee but otherwise tell teh user "it broke". // in the log so we can find this bit of codee but otherwise tell teh user "it broke".
@ -68,19 +149,31 @@ export default class AutoDiscoveryUtils {
const hsResult = discoveryResult['m.homeserver']; const hsResult = discoveryResult['m.homeserver'];
if (hsResult.state !== AutoDiscovery.SUCCESS) { if (hsResult.state !== AutoDiscovery.SUCCESS) {
if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) { console.error("Error processing homeserver config:", hsResult);
throw newTranslatableError(hsResult.error); if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(hsResult.error)) {
} if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) {
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); throw newTranslatableError(hsResult.error);
}
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
} // else the error is not related to syntax - continue anyways.
} }
const isResult = discoveryResult['m.identity_server']; // Note: In the cases where we rely on this pre-populated "https://vector.im" (namely
// lack of identity server provided by the discovery method), we intentionally do not
// validate it. We already know the IS is an IS, and this helps some off-the-grid usage
// of Riot.
let preferredIdentityUrl = "https://vector.im"; let preferredIdentityUrl = "https://vector.im";
const isResult = discoveryResult['m.identity_server'];
if (isResult && isResult.state === AutoDiscovery.SUCCESS) { if (isResult && isResult.state === AutoDiscovery.SUCCESS) {
preferredIdentityUrl = isResult["base_url"]; preferredIdentityUrl = isResult["base_url"];
} else if (isResult && isResult.state !== AutoDiscovery.PROMPT) { } else if (isResult && isResult.state !== AutoDiscovery.PROMPT) {
console.error("Error determining preferred identity server URL:", isResult); console.error("Error determining preferred identity server URL:", isResult);
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(isResult.error)) {
if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error) !== -1) {
throw newTranslatableError(isResult.error);
}
throw newTranslatableError(_td("Unexpected error resolving identity server configuration"));
} // else the error is not related to syntax - continue anyways.
} }
const preferredHomeserverUrl = hsResult["base_url"]; const preferredHomeserverUrl = hsResult["base_url"];