Have ServerConfig and co. do validation of the config in-house
This also causes the components to produce a ValidatedServerConfig for use by other components.
This commit is contained in:
parent
e8a94ca3cf
commit
636cb8a5cc
4 changed files with 238 additions and 74 deletions
|
@ -35,3 +35,8 @@ limitations under the License.
|
|||
.mx_ServerConfig_help:link {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.mx_ServerConfig_error {
|
||||
display: block;
|
||||
color: $warning-color;
|
||||
}
|
||||
|
|
|
@ -18,9 +18,15 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
|
||||
import * as ServerType from '../../views/auth/ServerTypeSelector';
|
||||
|
||||
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
|
||||
|
||||
// TODO: TravisR - Can this extend ServerConfig for most things?
|
||||
|
||||
/*
|
||||
* Configure the Modular server name.
|
||||
*
|
||||
|
@ -31,65 +37,87 @@ export default class ModularServerConfig extends React.PureComponent {
|
|||
static propTypes = {
|
||||
onServerConfigChange: PropTypes.func,
|
||||
|
||||
// default URLs are defined in config.json (or the hardcoded defaults)
|
||||
// they are used if the user has not overridden them with a custom URL.
|
||||
// In other words, if the custom URL is blank, the default is used.
|
||||
defaultHsUrl: PropTypes.string, // e.g. https://matrix.org
|
||||
|
||||
// This component always uses the default IS URL and doesn't allow it
|
||||
// to be changed. We still receive it as a prop here to simplify
|
||||
// consumers by still passing the IS URL via onServerConfigChange.
|
||||
defaultIsUrl: PropTypes.string, // e.g. https://vector.im
|
||||
|
||||
// custom URLs are explicitly provided by the user and override the
|
||||
// default URLs. The user enters them via the component's input fields,
|
||||
// which is reflected on these properties whenever on..UrlChanged fires.
|
||||
// They are persisted in localStorage by MatrixClientPeg, and so can
|
||||
// override the default URLs when the component initially loads.
|
||||
customHsUrl: PropTypes.string,
|
||||
// The current configuration that the user is expecting to change.
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
|
||||
delayTimeMs: PropTypes.number, // time to wait before invoking onChanged
|
||||
}
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onServerConfigChange: function() {},
|
||||
customHsUrl: "",
|
||||
delayTimeMs: 0,
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hsUrl: props.customHsUrl,
|
||||
busy: false,
|
||||
errorText: "",
|
||||
hsUrl: props.serverConfig.hsUrl,
|
||||
isUrl: props.serverConfig.isUrl,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.customHsUrl === this.state.hsUrl) return;
|
||||
if (newProps.serverConfig.hsUrl === this.state.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.state.isUrl) return;
|
||||
|
||||
this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
|
||||
}
|
||||
|
||||
async validateAndApplyServer(hsUrl, isUrl) {
|
||||
// Always try and use the defaults first
|
||||
const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
|
||||
if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) {
|
||||
this.setState({busy: false, errorText: ""});
|
||||
this.props.onServerConfigChange(defaultConfig);
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
hsUrl: newProps.customHsUrl,
|
||||
hsUrl,
|
||||
isUrl,
|
||||
busy: true,
|
||||
errorText: "",
|
||||
});
|
||||
this.props.onServerConfigChange({
|
||||
hsUrl: newProps.customHsUrl,
|
||||
isUrl: this.props.defaultIsUrl,
|
||||
|
||||
try {
|
||||
const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
|
||||
this.setState({busy: false, errorText: ""});
|
||||
this.props.onServerConfigChange(result);
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
let message = _t("Unable to validate homeserver/identity server");
|
||||
if (e.translatedMessage) {
|
||||
message = e.translatedMessage;
|
||||
}
|
||||
this.setState({
|
||||
busy: false,
|
||||
errorText: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async validateServer() {
|
||||
// TODO: Do we want to support .well-known lookups here?
|
||||
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
||||
// find their homeserver without demanding they use "https://matrix.org"
|
||||
return this.validateAndApplyServer(this.state.hsUrl, ServerType.TYPES.PREMIUM.identityServerUrl);
|
||||
}
|
||||
|
||||
onHomeserverBlur = (ev) => {
|
||||
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
|
||||
this.props.onServerConfigChange({
|
||||
hsUrl: this.state.hsUrl,
|
||||
isUrl: this.props.defaultIsUrl,
|
||||
this.validateServer();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onHomeserverChange = (ev) => {
|
||||
const hsUrl = ev.target.value;
|
||||
this.setState({ hsUrl });
|
||||
}
|
||||
};
|
||||
|
||||
_waitThenInvoke(existingTimeoutId, fn) {
|
||||
if (existingTimeoutId) {
|
||||
|
@ -116,7 +144,7 @@ export default class ModularServerConfig extends React.PureComponent {
|
|||
<div className="mx_ServerConfig_fields">
|
||||
<Field id="mx_ServerConfig_hsUrl"
|
||||
label={_t("Server Name")}
|
||||
placeholder={this.props.defaultHsUrl}
|
||||
placeholder={this.props.serverConfig.hsUrl}
|
||||
value={this.state.hsUrl}
|
||||
onBlur={this.onHomeserverBlur}
|
||||
onChange={this.onHomeserverChange}
|
||||
|
|
|
@ -20,6 +20,9 @@ import PropTypes from 'prop-types';
|
|||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
|
||||
/*
|
||||
* A pure UI component which displays the HS and IS to use.
|
||||
|
@ -29,80 +32,97 @@ export default class ServerConfig extends React.PureComponent {
|
|||
static propTypes = {
|
||||
onServerConfigChange: PropTypes.func,
|
||||
|
||||
// default URLs are defined in config.json (or the hardcoded defaults)
|
||||
// they are used if the user has not overridden them with a custom URL.
|
||||
// In other words, if the custom URL is blank, the default is used.
|
||||
defaultHsUrl: PropTypes.string, // e.g. https://matrix.org
|
||||
defaultIsUrl: PropTypes.string, // e.g. https://vector.im
|
||||
|
||||
// custom URLs are explicitly provided by the user and override the
|
||||
// default URLs. The user enters them via the component's input fields,
|
||||
// which is reflected on these properties whenever on..UrlChanged fires.
|
||||
// They are persisted in localStorage by MatrixClientPeg, and so can
|
||||
// override the default URLs when the component initially loads.
|
||||
customHsUrl: PropTypes.string,
|
||||
customIsUrl: PropTypes.string,
|
||||
// The current configuration that the user is expecting to change.
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
|
||||
delayTimeMs: PropTypes.number, // time to wait before invoking onChanged
|
||||
}
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onServerConfigChange: function() {},
|
||||
customHsUrl: "",
|
||||
customIsUrl: "",
|
||||
delayTimeMs: 0,
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hsUrl: props.customHsUrl,
|
||||
isUrl: props.customIsUrl,
|
||||
busy: false,
|
||||
errorText: "",
|
||||
hsUrl: props.serverConfig.hsUrl,
|
||||
isUrl: props.serverConfig.isUrl,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.customHsUrl === this.state.hsUrl &&
|
||||
newProps.customIsUrl === this.state.isUrl) return;
|
||||
if (newProps.serverConfig.hsUrl === this.state.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.state.isUrl) return;
|
||||
|
||||
this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
|
||||
}
|
||||
|
||||
async validateServer() {
|
||||
// TODO: Do we want to support .well-known lookups here?
|
||||
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
||||
// find their homeserver without demanding they use "https://matrix.org"
|
||||
return this.validateAndApplyServer(this.state.hsUrl, this.state.isUrl);
|
||||
}
|
||||
|
||||
async validateAndApplyServer(hsUrl, isUrl) {
|
||||
// Always try and use the defaults first
|
||||
const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
|
||||
if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) {
|
||||
this.setState({busy: false, errorText: ""});
|
||||
this.props.onServerConfigChange(defaultConfig);
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
hsUrl: newProps.customHsUrl,
|
||||
isUrl: newProps.customIsUrl,
|
||||
hsUrl,
|
||||
isUrl,
|
||||
busy: true,
|
||||
errorText: "",
|
||||
});
|
||||
this.props.onServerConfigChange({
|
||||
hsUrl: newProps.customHsUrl,
|
||||
isUrl: newProps.customIsUrl,
|
||||
|
||||
try {
|
||||
const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
|
||||
this.setState({busy: false, errorText: ""});
|
||||
this.props.onServerConfigChange(result);
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
let message = _t("Unable to validate homeserver/identity server");
|
||||
if (e.translatedMessage) {
|
||||
message = e.translatedMessage;
|
||||
}
|
||||
this.setState({
|
||||
busy: false,
|
||||
errorText: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onHomeserverBlur = (ev) => {
|
||||
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
|
||||
this.props.onServerConfigChange({
|
||||
hsUrl: this.state.hsUrl,
|
||||
isUrl: this.state.isUrl,
|
||||
this.validateServer();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onHomeserverChange = (ev) => {
|
||||
const hsUrl = ev.target.value;
|
||||
this.setState({ hsUrl });
|
||||
}
|
||||
};
|
||||
|
||||
onIdentityServerBlur = (ev) => {
|
||||
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, () => {
|
||||
this.props.onServerConfigChange({
|
||||
hsUrl: this.state.hsUrl,
|
||||
isUrl: this.state.isUrl,
|
||||
this.validateServer();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onIdentityServerChange = (ev) => {
|
||||
const isUrl = ev.target.value;
|
||||
this.setState({ isUrl });
|
||||
}
|
||||
};
|
||||
|
||||
_waitThenInvoke(existingTimeoutId, fn) {
|
||||
if (existingTimeoutId) {
|
||||
|
@ -114,11 +134,15 @@ export default class ServerConfig extends React.PureComponent {
|
|||
showHelpPopup = () => {
|
||||
const CustomServerDialog = sdk.getComponent('auth.CustomServerDialog');
|
||||
Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
|
||||
const errorText = this.state.errorText
|
||||
? <span className='mx_ServerConfig_error'>{this.state.errorText}</span>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="mx_ServerConfig">
|
||||
<h3>{_t("Other servers")}</h3>
|
||||
|
@ -127,20 +151,23 @@ export default class ServerConfig extends React.PureComponent {
|
|||
{ sub }
|
||||
</a>,
|
||||
})}
|
||||
{errorText}
|
||||
<div className="mx_ServerConfig_fields">
|
||||
<Field id="mx_ServerConfig_hsUrl"
|
||||
label={_t("Homeserver URL")}
|
||||
placeholder={this.props.defaultHsUrl}
|
||||
placeholder={this.props.serverConfig.hsUrl}
|
||||
value={this.state.hsUrl}
|
||||
onBlur={this.onHomeserverBlur}
|
||||
onChange={this.onHomeserverChange}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
<Field id="mx_ServerConfig_isUrl"
|
||||
label={_t("Identity Server URL")}
|
||||
placeholder={this.props.defaultIsUrl}
|
||||
placeholder={this.props.serverConfig.isUrl}
|
||||
value={this.state.isUrl}
|
||||
onBlur={this.onIdentityServerBlur}
|
||||
onChange={this.onIdentityServerChange}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
104
src/utils/AutoDiscoveryUtils.js
Normal file
104
src/utils/AutoDiscoveryUtils.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
Copyright 2019 New Vector 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 {AutoDiscovery} from "matrix-js-sdk";
|
||||
import {_td, newTranslatableError} from "../languageHandler";
|
||||
import {makeType} from "./TypeUtils";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
|
||||
export class ValidatedServerConfig {
|
||||
hsUrl: string;
|
||||
hsName: string;
|
||||
hsNameIsDifferent: string;
|
||||
|
||||
isUrl: string;
|
||||
identityEnabled: boolean;
|
||||
}
|
||||
|
||||
export default class AutoDiscoveryUtils {
|
||||
static async validateServerConfigWithStaticUrls(homeserverUrl: string, identityUrl: string): ValidatedServerConfig {
|
||||
if (!homeserverUrl) {
|
||||
throw newTranslatableError(_td("No homeserver URL provided"));
|
||||
}
|
||||
|
||||
const wellknownConfig = {
|
||||
"m.homeserver": {
|
||||
base_url: homeserverUrl,
|
||||
},
|
||||
"m.identity_server": {
|
||||
base_url: identityUrl,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await AutoDiscovery.fromDiscoveryConfig(wellknownConfig);
|
||||
|
||||
const url = new URL(homeserverUrl);
|
||||
const serverName = url.hostname;
|
||||
|
||||
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
|
||||
}
|
||||
|
||||
static async validateServerName(serverName: string): ValidatedServerConfig {
|
||||
const result = await AutoDiscovery.findClientConfig(serverName);
|
||||
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
|
||||
}
|
||||
|
||||
static buildValidatedConfigFromDiscovery(serverName: string, discoveryResult): ValidatedServerConfig {
|
||||
if (!discoveryResult || !discoveryResult["m.homeserver"]) {
|
||||
// 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".
|
||||
console.error("Ended up in a state of not knowing which homeserver to connect to.");
|
||||
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
|
||||
}
|
||||
|
||||
const hsResult = discoveryResult['m.homeserver'];
|
||||
if (hsResult.state !== AutoDiscovery.SUCCESS) {
|
||||
if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) {
|
||||
throw newTranslatableError(hsResult.error);
|
||||
}
|
||||
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
|
||||
}
|
||||
|
||||
const isResult = discoveryResult['m.identity_server'];
|
||||
let preferredIdentityUrl = "https://vector.im";
|
||||
if (isResult && isResult.state === AutoDiscovery.SUCCESS) {
|
||||
preferredIdentityUrl = isResult["base_url"];
|
||||
} else if (isResult && isResult.state !== AutoDiscovery.PROMPT) {
|
||||
console.error("Error determining preferred identity server URL:", isResult);
|
||||
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
|
||||
}
|
||||
|
||||
const preferredHomeserverUrl = hsResult["base_url"];
|
||||
let preferredHomeserverName = serverName ? serverName : hsResult["server_name"];
|
||||
|
||||
const url = new URL(preferredHomeserverUrl);
|
||||
if (!preferredHomeserverName) preferredHomeserverName = url.hostname;
|
||||
|
||||
// It should have been set by now, so check it
|
||||
if (!preferredHomeserverName) {
|
||||
console.error("Failed to parse homeserver name from homeserver URL");
|
||||
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
|
||||
}
|
||||
|
||||
return makeType(ValidatedServerConfig, {
|
||||
hsUrl: preferredHomeserverUrl,
|
||||
hsName: preferredHomeserverName,
|
||||
hsNameIsDifferent: url.hostname !== preferredHomeserverName,
|
||||
isUrl: preferredIdentityUrl,
|
||||
identityEnabled: !SdkConfig.get()['disable_identity_server'],
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue