From f08491cee87367b7680bdf031f4963071de57aba Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 15 Apr 2019 18:59:28 -0600 Subject: [PATCH] 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. --- README.md | 38 ++++---- config.sample.json | 12 ++- src/i18n/strings/en_EN.json | 7 +- src/vector/index.js | 186 ++++++++++++++++++++++++++++++++---- 4 files changed, 205 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 3fea9815..a662b319 100644 --- a/README.md +++ b/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:///.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:///.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: diff --git a/config.sample.json b/config.sample.json index c1aedee0..d4eb8df0 100644 --- a/config.sample.json +++ b/config.sample.json @@ -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, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9c467cfc..abb82e8f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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" } diff --git a/src/vector/index.js b/src/vector/index.js index 9d5c1dd4..1e8c4b6c 100644 --- a/src/vector/index.js +++ b/src/vector/index.js @@ -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( - , - 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( + , + 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( + , + 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();