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 default homeserver config before loading the app #9496

Merged
merged 10 commits into from
May 10, 2019
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
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`
turt2live marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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"
}
112 changes: 96 additions & 16 deletions src/vector/index.js
Original file line number Diff line number Diff line change
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';
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,66 @@ async function loadLanguage() {
}
}

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

// TODO: TravisR - Handle query string arguments for hs_url and is_url
// We probably don't want to handle them unless the user is logged out though?

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

// 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();
16 changes: 15 additions & 1 deletion test/app-tests/joining.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-addons-test-utils');
const expect = require('expect');
import Promise from 'bluebird';
import {makeType} from "matrix-react-sdk/lib/utils/TypeUtils";
import {ValidatedServerConfig} from "matrix-react-sdk/lib/utils/AutoDiscoveryUtils";

const test_utils = require('../test-utils');
const MockHttpBackend = require('matrix-mock-request');
Expand Down Expand Up @@ -96,8 +98,20 @@ describe('joining a room', function() {

PlatformPeg.set(new WebPlatform());

const config = {
validated_server_config: makeType(ValidatedServerConfig, {
hsUrl: HS_URL,
hsName: "TEST_ENVIRONMENT",
hsNameIsDifferent: false, // yes, we lie
isUrl: IS_URL,
identityEnabled: true,
}),
};

const mc = (
<MatrixChat config={{}}
<MatrixChat
config={config}
serverConfig={config.validated_server_config}
makeRegistrationUrl={()=>{throw new Error("unimplemented");}}
initialScreenAfterLogin={{
screen: 'directory',
Expand Down
22 changes: 21 additions & 1 deletion test/app-tests/loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import dis from 'matrix-react-sdk/lib/dispatcher';
import * as test_utils from '../test-utils';
import MockHttpBackend from 'matrix-mock-request';
import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils';
import {makeType} from "matrix-react-sdk/lib/utils/TypeUtils";
import {ValidatedServerConfig} from "matrix-react-sdk/lib/utils/AutoDiscoveryUtils";

const DEFAULT_HS_URL='http://my_server';
const DEFAULT_IS_URL='http://my_is';
Expand Down Expand Up @@ -146,6 +148,13 @@ describe('loading:', function() {
const config = Object.assign({
default_hs_url: DEFAULT_HS_URL,
default_is_url: DEFAULT_IS_URL,
validated_server_config: makeType(ValidatedServerConfig, {
hsUrl: DEFAULT_HS_URL,
hsName: "TEST_ENVIRONMENT",
hsNameIsDifferent: false, // yes, we lie
isUrl: DEFAULT_IS_URL,
identityEnabled: true,
}),
embeddedPages: {
homeUrl: 'data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==',
},
Expand All @@ -160,6 +169,7 @@ describe('loading:', function() {
<MatrixChat
onNewScreen={onNewScreen}
config={config}
serverConfig={config.validated_server_config}
realQueryParams={params}
startingFragmentQueryParams={fragParts.params}
enableGuest={true}
Expand Down Expand Up @@ -616,11 +626,21 @@ describe('loading:', function() {

// check that we have a Login component, send a 'user:pass' login,
// and await the HTTP requests.
function completeLogin(matrixChat) {
async function completeLogin(matrixChat) {
// we expect a single <Login> component
const login = ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.auth.Login'));

// When we switch to the login component, it'll hit the login endpoint
// for proof of life and to get flows. We'll only give it one option.
httpBackend.when('GET', '/login')
.respond(200, {"flows": [{"type": "m.login.password"}]});
httpBackend.flush(); // We already would have tried the GET /login request

// Give the component some time to finish processing the login flows before
// continuing.
await Promise.delay(100);

httpBackend.when('POST', '/login').check(function(req) {
expect(req.data.type).toEqual('m.login.password');
expect(req.data.identifier.type).toEqual('m.id.user');
Expand Down