mirror of https://github.com/vector-im/riot-web
Validate default homeserver config before loading the app
Implements the process described here: https://github.com/vector-im/riot-web/issues/9290#issuecomment-481966910 The expectation is that later layers (like the react-sdk) will make use of the `validated_discovery_config` option instead of interpreting the config themselves. We intentionally block the UI from loading here to avoid races between discovery and the app loading.pull/9496/head
parent
9cd4ac1df4
commit
f08491cee8
38
README.md
38
README.md
|
@ -109,25 +109,29 @@ You can configure the app by copying `config.sample.json` to
|
|||
|
||||
For a good example, see https://riot.im/develop/config.json.
|
||||
|
||||
1. `default_server_name` sets the default server name to use for authentication.
|
||||
This will trigger Riot to ask
|
||||
`https://<server_name>/.well-known/matrix/client` for the homeserver and
|
||||
identity server URLs to use. This is the recommended approach for setting a
|
||||
default server. However, it is also possible to use the following to directly
|
||||
configure each of the URLs:
|
||||
* `default_hs_url` sets the default homeserver URL.
|
||||
* `default_is_url` sets the default identity server URL (this is the server used
|
||||
for verifying third party identifiers like email addresses). If this is blank,
|
||||
registering with an email address, adding an email address to your account,
|
||||
or inviting users via email address will not work. Matrix identity servers are
|
||||
very simple web services which map third party identifiers (currently only email
|
||||
addresses) to matrix IDs: see http://matrix.org/docs/spec/identity_service/unstable.html
|
||||
for more details. Currently the only public matrix identity servers are https://matrix.org
|
||||
and https://vector.im. In the future, identity servers will be decentralised.
|
||||
* Riot will report an error if you accidentally configure both `default_server_name` _and_ `default_hs_url` since it's unclear which should take priority.
|
||||
1. `default_server_config` sets the default homeserver and identity server URL for
|
||||
Riot to use. The object is the same as returned by [https://<server_name>/.well-known/matrix/client](https://matrix.org/docs/spec/client_server/latest.html#get-well-known-matrix-client),
|
||||
with added support for a `server_name` under the `m.homeserver` section to display
|
||||
a custom homeserver name. Alternatively, the config can contain a `default_server_name`
|
||||
instead which is where Riot will go to get that same object - see the `.well-known`
|
||||
link above for more information.
|
||||
* *Note*: The URLs can also be individually specified as `default_hs_url` and
|
||||
`default_is_url`, however these are deprecated. They are maintained for backwards
|
||||
compatibility with older configurations. `default_is_url` is respected only
|
||||
if `default_hs_url` is used.
|
||||
* The identity server is used for verifying third party identifiers like emails
|
||||
and phone numbers. It is not used to store your password or account information.
|
||||
If not provided, the identity server defaults to vector.im unless `disable_identity_server`
|
||||
is set to true in the config. Currently the only two public identity servers
|
||||
are https://matrix.org and https://vector.im, however in future identity servers
|
||||
will be decentralised.
|
||||
* Riot will fail to load if a mix of `default_server_config`, `default_server_name`, or
|
||||
`default_hs_url` is specified. When multiple sources are specified, it is unclear
|
||||
which should take priority and therefore the application cannot continue.
|
||||
1. `features`: Lookup of optional features that may be `enable`d, `disable`d, or exposed to the user
|
||||
in the `labs` section of settings. The available optional experimental features vary from
|
||||
release to release.
|
||||
release to release. Some of the available features are described in the Labs Feature section
|
||||
of this README.
|
||||
1. `brand`: String to pass to your homeserver when configuring email notifications, to let the
|
||||
homeserver know what email template to use when talking to you.
|
||||
1. `branding`: Configures various branding and logo details, such as:
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
{
|
||||
"default_hs_url": "https://matrix.org",
|
||||
"default_is_url": "https://vector.im",
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.org",
|
||||
"server_name": "matrix.org"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https://vector.im"
|
||||
}
|
||||
},
|
||||
"disable_identity_server": false,
|
||||
"disable_custom_urls": false,
|
||||
"disable_guests": false,
|
||||
"disable_login_language_selector": false,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"Unexpected error preparing the app. See console for details.": "Unexpected error preparing the app. See console for details.",
|
||||
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.",
|
||||
"Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration",
|
||||
"Riot Desktop on %(platformName)s": "Riot Desktop on %(platformName)s",
|
||||
"Unknown device": "Unknown device",
|
||||
"%(appName)s via %(browserName)s on %(osName)s": "%(appName)s via %(browserName)s on %(osName)s",
|
||||
|
@ -15,7 +18,5 @@
|
|||
"Need help?": "Need help?",
|
||||
"Chat with Riot Bot": "Chat with Riot Bot",
|
||||
"Explore rooms": "Explore rooms",
|
||||
"Room Directory": "Room Directory",
|
||||
"Search the room directory": "Search the room directory",
|
||||
"Get started with some tips from Riot Bot!": "Get started with some tips from Riot Bot!"
|
||||
"Room Directory": "Room Directory"
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ import VectorConferenceHandler from 'matrix-react-sdk/lib/VectorConferenceHandle
|
|||
import Promise from 'bluebird';
|
||||
import request from 'browser-request';
|
||||
import * as languageHandler from 'matrix-react-sdk/lib/languageHandler';
|
||||
import {_t, _td} from 'matrix-react-sdk/lib/languageHandler';
|
||||
import {AutoDiscovery} from "matrix-js-sdk/lib/autodiscovery";
|
||||
|
||||
import url from 'url';
|
||||
|
||||
|
@ -341,22 +343,37 @@ async function loadApp() {
|
|||
const platform = PlatformPeg.get();
|
||||
platform.startUpdater();
|
||||
|
||||
const MatrixChat = sdk.getComponent('structures.MatrixChat');
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<MatrixChat
|
||||
onNewScreen={onNewScreen}
|
||||
makeRegistrationUrl={makeRegistrationUrl}
|
||||
ConferenceHandler={VectorConferenceHandler}
|
||||
config={configJson}
|
||||
realQueryParams={params}
|
||||
startingFragmentQueryParams={fragparts.params}
|
||||
enableGuest={!configJson.disable_guests}
|
||||
onTokenLoginCompleted={onTokenLoginCompleted}
|
||||
initialScreenAfterLogin={getScreenFromLocation(window.location)}
|
||||
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
|
||||
/>,
|
||||
document.getElementById('matrixchat'),
|
||||
);
|
||||
// Don't bother loading the app until the config is verified
|
||||
verifyServerConfig().then((newConfig) => {
|
||||
const MatrixChat = sdk.getComponent('structures.MatrixChat');
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<MatrixChat
|
||||
onNewScreen={onNewScreen}
|
||||
makeRegistrationUrl={makeRegistrationUrl}
|
||||
ConferenceHandler={VectorConferenceHandler}
|
||||
config={newConfig}
|
||||
realQueryParams={params}
|
||||
startingFragmentQueryParams={fragparts.params}
|
||||
enableGuest={!configJson.disable_guests}
|
||||
onTokenLoginCompleted={onTokenLoginCompleted}
|
||||
initialScreenAfterLogin={getScreenFromLocation(window.location)}
|
||||
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
|
||||
/>,
|
||||
document.getElementById('matrixchat'),
|
||||
);
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
|
||||
const errorMessage = err.translatedMessage
|
||||
|| _t("Unexpected error preparing the app. See console for details.");
|
||||
|
||||
// Like the compatibility page, AWOOOOOGA at the user
|
||||
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<GenericErrorPage message={errorMessage} />,
|
||||
document.getElementById('matrixchat'),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
console.error("Browser is missing required features.");
|
||||
// take to a different landing page to AWOOOOOGA at the user
|
||||
|
@ -428,4 +445,141 @@ async function loadLanguage() {
|
|||
}
|
||||
}
|
||||
|
||||
async function verifyServerConfig() {
|
||||
console.log("Verifying homeserver configuration");
|
||||
|
||||
// Errors which can be returned by .well-known lookups. If autodiscovery fails for unexpected reasons,
|
||||
// the last thing we want is "missing-translation|en:Your error here". The actual strings are also defined
|
||||
// in the react-sdk, so we don't need them here.
|
||||
const discoveryErrors = [
|
||||
"Invalid homeserver discovery response",
|
||||
"Failed to get autodiscovery configuration from server",
|
||||
"Invalid base_url for m.homeserver",
|
||||
"Homeserver URL does not appear to be a valid Matrix homeserver",
|
||||
"Invalid identity server discovery response",
|
||||
"Invalid base_url for m.identity_server",
|
||||
"Identity server URL does not appear to be a valid identity server",
|
||||
"General failure",
|
||||
];
|
||||
|
||||
const config = SdkConfig.get();
|
||||
let wkConfig = config['default_server_config']; // overwritten later under some conditions
|
||||
const serverName = config['default_server_name'];
|
||||
const hsUrl = config['default_hs_url'];
|
||||
const isUrl = config['default_is_url'];
|
||||
|
||||
const incompatibleOptions = [wkConfig, serverName, hsUrl].filter(i => !!i);
|
||||
if (incompatibleOptions.length > 1) {
|
||||
throw newTranslatableError(_td(
|
||||
"Invalid configuration: can only specify one of default_server_config, default_server_name, " +
|
||||
"or default_hs_url.",
|
||||
));
|
||||
}
|
||||
|
||||
if (hsUrl) {
|
||||
console.log("Config uses a default_hs_url - constructing a default_server_config using this information");
|
||||
|
||||
wkConfig = {
|
||||
"m.homeserver": {
|
||||
"base_url": hsUrl,
|
||||
},
|
||||
};
|
||||
if (isUrl) {
|
||||
wkConfig["m.identity_server"] = {
|
||||
"base_url": isUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let result = null;
|
||||
|
||||
if (wkConfig) {
|
||||
console.log("Config uses a default_server_config - validating object");
|
||||
result = await AutoDiscovery.fromDiscoveryConfig(wkConfig);
|
||||
}
|
||||
|
||||
if (serverName) {
|
||||
console.log("Config uses a default_server_name - doing .well-known lookup");
|
||||
result = await AutoDiscovery.findClientConfig(serverName);
|
||||
}
|
||||
|
||||
if (!result || !result["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 = result['m.homeserver'];
|
||||
if (hsResult.state !== AutoDiscovery.SUCCESS) {
|
||||
if (discoveryErrors.indexOf(hsResult.error) !== -1) {
|
||||
throw newTranslatableError(hsResult.error);
|
||||
}
|
||||
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
|
||||
}
|
||||
|
||||
const isResult = result['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"));
|
||||
}
|
||||
|
||||
const isServerNameDifferentFromUrl = url.hostname !== preferredHomeserverName;
|
||||
|
||||
console.log("Using homeserver config:", {
|
||||
isServerNameDifferentFromUrl,
|
||||
preferredHomeserverName,
|
||||
preferredHomeserverUrl,
|
||||
preferredIdentityUrl,
|
||||
});
|
||||
|
||||
// Build our own discovery result for distribution within the app
|
||||
const configResult = {
|
||||
"m.homeserver": {
|
||||
"base_url": preferredHomeserverUrl,
|
||||
"server_name": preferredHomeserverName,
|
||||
"server_name_different": isServerNameDifferentFromUrl,
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": preferredIdentityUrl,
|
||||
"enabled": !SdkConfig.get()['disable_identity_server'],
|
||||
},
|
||||
};
|
||||
|
||||
// Copy over any other keys that may be of interest
|
||||
for (const key of Object.keys(result)) {
|
||||
if (key === "m.homeserver" || key === "m.identity_server") continue;
|
||||
configResult[key] = JSON.parse(JSON.stringify(result[key])); // deep clone
|
||||
}
|
||||
|
||||
// Add the newly built config to the actual config for use by the app
|
||||
console.log("Updating SdkConfig with validated discovery information");
|
||||
SdkConfig.add({"validated_discovery_config": configResult});
|
||||
|
||||
return SdkConfig.get();
|
||||
}
|
||||
|
||||
// Helper function to provide English errors in logs, but present translated
|
||||
// errors to users.
|
||||
function newTranslatableError(message) {
|
||||
const error = new Error(message);
|
||||
error.translatedMessage = _t(message);
|
||||
return error;
|
||||
}
|
||||
|
||||
loadApp();
|
||||
|
|
Loading…
Reference in New Issue