Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate homeserver configuration prior to loading the app #9779

Merged
merged 22 commits into from May 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f08491c
Validate default homeserver config before loading the app
turt2live Apr 16, 2019
5445632
Use new js-sdk properties for getting discovery errors
turt2live Apr 16, 2019
51941ea
Add note to self
turt2live Apr 17, 2019
c8caf67
Move bulk of discovery processing to react-sdk
turt2live Apr 18, 2019
e7bf643
Don't repackage the ValidatedDiscoveryConfig
turt2live Apr 18, 2019
ab6ba0e
Add some clarity to the config options
turt2live Apr 19, 2019
45caa8b
Add a couple notes
turt2live May 2, 2019
bf97fd6
Handle cases of no default server
turt2live May 3, 2019
a5092dc
Fix tests to use new server config options
turt2live May 3, 2019
fc5c93a
Fix unit tests for new login
turt2live May 3, 2019
886828c
Merge pull request #9496 from vector-im/travis/guests/block-ui
turt2live May 10, 2019
7f5589c
Clarify comment on is_url and hs_url handling
turt2live May 14, 2019
68a7550
Flag the validated config as the default config
turt2live May 14, 2019
559416d
Show most homeserver options on the mobile guide
turt2live May 14, 2019
22cc33e
Do a simple .well-known lookup for default_server_name for mobile guide
turt2live May 14, 2019
51f5671
Merge pull request #9719 from vector-im/travis/wk/clarify_is_hs_url
turt2live May 16, 2019
39d1293
Merge pull request #9721 from vector-im/travis/wk/fallback_hs
turt2live May 16, 2019
1ac5df3
Merge branch 'develop' into travis/feature/wellknown2
turt2live May 16, 2019
7a65dc9
Merge pull request #9726 from vector-im/travis/wk/mobileguide
turt2live May 17, 2019
fa9d2a8
Merge branch 'develop' into travis/feature/wellknown2
turt2live May 21, 2019
b772bc7
Update copyright
turt2live May 23, 2019
26d8056
Merge branch 'develop' into travis/feature/wellknown2
turt2live May 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 23 additions & 17 deletions README.md
Expand Up @@ -109,25 +109,31 @@ 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 that the `default_server_name` is used to get
a complete server configuration whereas the `server_name` in the `default_server_config`
is for display purposes only.
* *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:
Expand Down
12 changes: 10 additions & 2 deletions 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,
Expand Down
7 changes: 4 additions & 3 deletions 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.",
"Invalid configuration: no default server specified.": "Invalid configuration: no default server specified.",
"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",
Expand All @@ -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"
}
116 changes: 99 additions & 17 deletions src/vector/index.js
@@ -1,7 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2018, 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.
Expand Down Expand Up @@ -45,6 +45,9 @@ 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, newTranslatableError} from 'matrix-react-sdk/lib/languageHandler';
turt2live marked this conversation as resolved.
Show resolved Hide resolved
import AutoDiscoveryUtils from 'matrix-react-sdk/lib/utils/AutoDiscoveryUtils';
import {AutoDiscovery} from "matrix-js-sdk/lib/autodiscovery";

import url from 'url';

Expand Down Expand Up @@ -341,22 +344,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
Expand Down Expand Up @@ -428,4 +446,68 @@ async function loadLanguage() {
}
}

async function verifyServerConfig() {
console.log("Verifying homeserver configuration");

// Note: the query string may include is_url and hs_url - we only respect these in the
// context of email validation. Because we don't respect them otherwise, we do not need
// to parse or consider them here.

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 (incompatibleOptions.length < 1) {
throw newTranslatableError(_td("Invalid configuration: no default server specified."));
}

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);
}

const validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
validatedConfig.isDefault = true;

// Just in case we ever have to debug this
console.log("Using homeserver config:", validatedConfig);

// 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_server_config": validatedConfig});

return SdkConfig.get();
}

loadApp();
21 changes: 20 additions & 1 deletion src/vector/mobile_guide/index.html
Expand Up @@ -4,6 +4,11 @@

<style type="text/css">

/* By default, hide the custom IS stuff - enabled in JS */
#custom_is, #is_url {
display: none;
}

body {
background: #c5e0f7;
background: -moz-linear-gradient(top, #c5e0f7 0%, #ffffff 100%);
Expand Down Expand Up @@ -109,6 +114,14 @@
margin: 20px;
}

.mx_HomePage_errorContainer {
display: none; /* shown in JS if needed */
margin: 20px;
border: 1px solid red;
background-color: #ffb9b9;
padding: 5px;
}

.mx_HomePage_container h1,
.mx_HomePage_container h2,
.mx_HomePage_container h3,
Expand Down Expand Up @@ -152,6 +165,10 @@

<body>

<div class="mx_HomePage_errorContainer">
<!-- populated by JS if needed -->
</div>

<div class="mx_HomePage_container">
<div class="mx_HomePage_col mx_HomePage_header">
<a href="https://riot.im">
Expand Down Expand Up @@ -365,7 +382,9 @@ <h2>Step 2: Use a custom server</h2>
<p>Launch the app, and enable <strong>Use custom server options (advanced)</strong>.</p>
<p class="mx_Spacer">In the homeserver field, enter:</p>
<p><strong id="hs_url"></strong></p>
<p class="mx_Spacer"><em>Note: You shouldn&apos;t need to modify the identity server field, which by default is set to https://vector.im.</em></p>
<p class="mx_Spacer" id="default_is"><em>Note: You shouldn&apos;t need to modify the identity server field, which by default is set to https://vector.im.</em></p>
<p class="mx_Spacer" id="custom_is">In the identity server field, enter:</p>
<p><strong id="is_url"></strong></p>
</div>
</div>
</div>
Expand Down
95 changes: 89 additions & 6 deletions src/vector/mobile_guide/index.js
Expand Up @@ -6,19 +6,102 @@ function onBackToRiotClick() {
window.location.href = '../';
}

// NEVER pass user-controlled content to this function! Hardcoded strings only please.
function renderConfigError(message) {
const contactMsg = "If this is unexpected, please contact your system administrator " +
"or technical support representative.";
message = `<h2>Error loading Riot</h2><p>${message}</p><p>${contactMsg}</p>`;

const toHide = document.getElementsByClassName("mx_HomePage_container");
const errorContainers = document.getElementsByClassName("mx_HomePage_errorContainer");

for (const e of toHide) {
// We have to clear the content because .style.display='none'; doesn't work
// due to an !important in the CSS.
e.innerHTML = '';
}
for (const e of errorContainers) {
e.style.display = 'block';
e.innerHTML = message;
}
}

async function initPage() {
document.getElementById('back_to_riot_button').onclick = onBackToRiotClick;

const config = await getVectorConfig('..');
let hsUrl;
if (config && config['default_hs_url']) {
hsUrl = config['default_hs_url'];
let config = await getVectorConfig('..');

// We manually parse the config similar to how validateServerConfig works because
// calling that function pulls in roughly 4mb of JS we don't use.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One day we'll actually fix our build to make sense... 😭


const wkConfig = config['default_server_config']; // overwritten later under some conditions
const serverName = config['default_server_name'];
const defaultHsUrl = config['default_hs_url'];
const defaultIsUrl = config['default_is_url'];

const incompatibleOptions = [wkConfig, serverName, defaultHsUrl].filter(i => !!i);
if (incompatibleOptions.length > 1) {
return renderConfigError(
"Invalid configuration: can only specify one of default_server_config, default_server_name, " +
"or default_hs_url.",
);
}
if (incompatibleOptions.length < 1) {
return renderConfigError("Invalid configuration: no default server specified.");
}

let hsUrl = '';
let isUrl = '';

if (wkConfig && wkConfig['m.homeserver']) {
hsUrl = wkConfig['m.homeserver']['base_url'];

if (wkConfig['m.identity_server']) {
isUrl = wkConfig['m.identity_server']['base_url'];
}
}

if (serverName) {
// We also do our own minimal .well-known validation to avoid pulling in the js-sdk
try {
const result = await fetch(`https://${serverName}/.well-known/matrix/client`);
const wkConfig = await result.json();
if (wkConfig && wkConfig['m.homeserver']) {
hsUrl = wkConfig['m.homeserver']['base_url'];

if (wkConfig['m.identity_server']) {
isUrl = wkConfig['m.identity_server']['base_url'];
}
}
} catch (e) {
console.error(e);
return renderConfigError("Unable to fetch homeserver configuration");
}
}

if (defaultHsUrl) {
hsUrl = defaultHsUrl;
isUrl = defaultIsUrl;
}

if (!hsUrl) {
return renderConfigError("Unable to locate homeserver");
}

if (hsUrl && !hsUrl.endsWith('/')) hsUrl += '/';
if (hsUrl && hsUrl !== 'https://matrix.org/') {
if (isUrl && !isUrl.endsWith('/')) isUrl += '/';

if (hsUrl !== 'https://matrix.org/') {
document.getElementById('step2_container').style.display = 'block';
document.getElementById('hs_url').innerHTML = hsUrl;
document.getElementById('hs_url').innerText = hsUrl;
document.getElementById('step_login_header').innerHTML= 'Step 3: Register or Log in';

if (isUrl && isUrl !== "https://vector.im/") {
document.getElementById('default_is').style.display = 'none';
document.getElementById('custom_is').style.display = 'block';
document.getElementById('is_url').style.display = 'block';
document.getElementById('is_url').innerText = isUrl;
}
}
}

Expand Down