UNPKG

23.4 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.AutoDiscovery = undefined;
7
8var _bluebird = require("bluebird");
9
10var _bluebird2 = _interopRequireDefault(_bluebird);
11
12var _regenerator = require("babel-runtime/regenerator");
13
14var _regenerator2 = _interopRequireDefault(_regenerator);
15
16var _keys = require("babel-runtime/core-js/object/keys");
17
18var _keys2 = _interopRequireDefault(_keys);
19
20var _createClass2 = require("babel-runtime/helpers/createClass");
21
22var _createClass3 = _interopRequireDefault(_createClass2);
23
24var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
25
26var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
27
28var _url = require("url");
29
30function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
32var logger = require("./logger"); /*
33 Copyright 2018 New Vector Ltd
34
35 Licensed under the Apache License, Version 2.0 (the "License");
36 you may not use this file except in compliance with the License.
37 You may obtain a copy of the License at
38
39 http://www.apache.org/licenses/LICENSE-2.0
40
41 Unless required by applicable law or agreed to in writing, software
42 distributed under the License is distributed on an "AS IS" BASIS,
43 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 See the License for the specific language governing permissions and
45 limitations under the License.
46 */
47
48/** @module auto-discovery */
49
50// Dev note: Auto discovery is part of the spec.
51// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
52
53/**
54 * Description for what an automatically discovered client configuration
55 * would look like. Although this is a class, it is recommended that it
56 * be treated as an interface definition rather than as a class.
57 *
58 * Additional properties than those defined here may be present, and
59 * should follow the Java package naming convention.
60 */
61var DiscoveredClientConfig = // eslint-disable-line no-unused-vars
62// Dev note: this is basically a copy/paste of the .well-known response
63// object as defined in the spec. It does have additional information,
64// however. Overall, this exists to serve as a place for documentation
65// and not functionality.
66// See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-well-known-matrix-client
67
68function DiscoveredClientConfig() {
69 (0, _classCallCheck3.default)(this, DiscoveredClientConfig);
70
71 /**
72 * The homeserver configuration the client should use. This will
73 * always be present on the object.
74 * @type {{state: string, base_url: string}} The configuration.
75 */
76 this["m.homeserver"] = {
77 /**
78 * The lookup result state. If this is anything other than
79 * AutoDiscovery.SUCCESS then base_url may be falsey. Additionally,
80 * if this is not AutoDiscovery.SUCCESS then the client should
81 * assume the other properties in the client config (such as
82 * the identity server configuration) are not valid.
83 */
84 state: AutoDiscovery.PROMPT,
85
86 /**
87 * If the state is AutoDiscovery.FAIL_ERROR or .FAIL_PROMPT
88 * then this will contain a human-readable (English) message
89 * for what went wrong. If the state is none of those previously
90 * mentioned, this will be falsey.
91 */
92 error: "Something went wrong",
93
94 /**
95 * The base URL clients should use to talk to the homeserver,
96 * particularly for the login process. May be falsey if the
97 * state is not AutoDiscovery.SUCCESS.
98 */
99 base_url: "https://matrix.org"
100 };
101
102 /**
103 * The identity server configuration the client should use. This
104 * will always be present on teh object.
105 * @type {{state: string, base_url: string}} The configuration.
106 */
107 this["m.identity_server"] = {
108 /**
109 * The lookup result state. If this is anything other than
110 * AutoDiscovery.SUCCESS then base_url may be falsey.
111 */
112 state: AutoDiscovery.PROMPT,
113
114 /**
115 * The base URL clients should use for interacting with the
116 * identity server. May be falsey if the state is not
117 * AutoDiscovery.SUCCESS.
118 */
119 base_url: "https://vector.im"
120 };
121};
122
123/**
124 * Utilities for automatically discovery resources, such as homeservers
125 * for users to log in to.
126 */
127
128
129var AutoDiscovery = exports.AutoDiscovery = function () {
130 function AutoDiscovery() {
131 (0, _classCallCheck3.default)(this, AutoDiscovery);
132 }
133
134 (0, _createClass3.default)(AutoDiscovery, null, [{
135 key: "findClientConfig",
136
137
138 /**
139 * Attempts to automatically discover client configuration information
140 * prior to logging in. Such information includes the homeserver URL
141 * and identity server URL the client would want. Additional details
142 * may also be discovered, and will be transparently included in the
143 * response object unaltered.
144 * @param {string} domain The homeserver domain to perform discovery
145 * on. For example, "matrix.org".
146 * @return {Promise<DiscoveredClientConfig>} Resolves to the discovered
147 * configuration, which may include error states. Rejects on unexpected
148 * failure, not when discovery fails.
149 */
150 value: function () {
151 var _ref = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee(domain) {
152 var clientConfig, wellknown, hsUrl, hsVersions, isUrl, failingClientConfig, isResponse;
153 return _regenerator2.default.wrap(function _callee$(_context) {
154 while (1) {
155 switch (_context.prev = _context.next) {
156 case 0:
157 if (!(!domain || typeof domain !== "string" || domain.length === 0)) {
158 _context.next = 2;
159 break;
160 }
161
162 throw new Error("'domain' must be a string of non-zero length");
163
164 case 2:
165
166 // We use a .well-known lookup for all cases. According to the spec, we
167 // can do other discovery mechanisms if we want such as custom lookups
168 // however we won't bother with that here (mostly because the spec only
169 // supports .well-known right now).
170 //
171 // By using .well-known, we need to ensure we at least pull out a URL
172 // for the homeserver. We don't really need an identity server configuration
173 // but will return one anyways (with state PROMPT) to make development
174 // easier for clients. If we can't get a homeserver URL, all bets are
175 // off on the rest of the config and we'll assume it is invalid too.
176
177 // We default to an error state to make the first few checks easier to
178 // write. We'll update the properties of this object over the duration
179 // of this function.
180 clientConfig = {
181 "m.homeserver": {
182 state: AutoDiscovery.FAIL_ERROR,
183 error: "Invalid homeserver discovery response",
184 base_url: null
185 },
186 "m.identity_server": {
187 // Technically, we don't have a problem with the identity server
188 // config at this point.
189 state: AutoDiscovery.PROMPT,
190 error: null,
191 base_url: null
192 }
193 };
194
195 // Step 1: Actually request the .well-known JSON file and make sure it
196 // at least has a homeserver definition.
197
198 _context.next = 5;
199 return (0, _bluebird.resolve)(this._fetchWellKnownObject("https://" + domain + "/.well-known/matrix/client"));
200
201 case 5:
202 wellknown = _context.sent;
203
204 if (!(!wellknown || wellknown.action !== "SUCCESS" || !wellknown.raw["m.homeserver"] || !wellknown.raw["m.homeserver"]["base_url"])) {
205 _context.next = 11;
206 break;
207 }
208
209 logger.error("No m.homeserver key in well-known response");
210 if (wellknown.reason) logger.error(wellknown.reason);
211 if (wellknown.action === "IGNORE") {
212 clientConfig["m.homeserver"] = {
213 state: AutoDiscovery.PROMPT,
214 error: null,
215 base_url: null
216 };
217 } else {
218 // this can only ever be FAIL_PROMPT at this point.
219 clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT;
220 }
221 return _context.abrupt("return", _bluebird2.default.resolve(clientConfig));
222
223 case 11:
224
225 // Step 2: Make sure the homeserver URL is valid *looking*. We'll make
226 // sure it points to a homeserver in Step 3.
227 hsUrl = this._sanitizeWellKnownUrl(wellknown.raw["m.homeserver"]["base_url"]);
228
229 if (hsUrl) {
230 _context.next = 15;
231 break;
232 }
233
234 logger.error("Invalid base_url for m.homeserver");
235 return _context.abrupt("return", _bluebird2.default.resolve(clientConfig));
236
237 case 15:
238 _context.next = 17;
239 return (0, _bluebird.resolve)(this._fetchWellKnownObject(hsUrl + "/_matrix/client/versions"));
240
241 case 17:
242 hsVersions = _context.sent;
243
244 if (!(!hsVersions || !hsVersions.raw["versions"])) {
245 _context.next = 21;
246 break;
247 }
248
249 logger.error("Invalid /versions response");
250 return _context.abrupt("return", _bluebird2.default.resolve(clientConfig));
251
252 case 21:
253
254 // Step 4: Now that the homeserver looks valid, update our client config.
255 clientConfig["m.homeserver"] = {
256 state: AutoDiscovery.SUCCESS,
257 error: null,
258 base_url: hsUrl
259 };
260
261 // Step 5: Try to pull out the identity server configuration
262 isUrl = "";
263
264 if (!wellknown.raw["m.identity_server"]) {
265 _context.next = 35;
266 break;
267 }
268
269 // We prepare a failing identity server response to save lines later
270 // in this branch. Note that we also fail the homeserver check in the
271 // object because according to the spec we're supposed to FAIL_ERROR
272 // if *anything* goes wrong with the IS validation, including invalid
273 // format. This means we're supposed to stop discovery completely.
274 failingClientConfig = {
275 "m.homeserver": {
276 state: AutoDiscovery.FAIL_ERROR,
277 error: "Invalid identity server discovery response",
278
279 // We'll provide the base_url that was previously valid for
280 // debugging purposes.
281 base_url: clientConfig["m.homeserver"].base_url
282 },
283 "m.identity_server": {
284 state: AutoDiscovery.FAIL_ERROR,
285 error: "Invalid identity server discovery response",
286 base_url: null
287 }
288 };
289
290 // Step 5a: Make sure the URL is valid *looking*. We'll make sure it
291 // points to an identity server in Step 5b.
292
293 isUrl = this._sanitizeWellKnownUrl(wellknown.raw["m.identity_server"]["base_url"]);
294
295 if (isUrl) {
296 _context.next = 29;
297 break;
298 }
299
300 logger.error("Invalid base_url for m.identity_server");
301 return _context.abrupt("return", _bluebird2.default.resolve(failingClientConfig));
302
303 case 29:
304 _context.next = 31;
305 return (0, _bluebird.resolve)(this._fetchWellKnownObject(isUrl + "/_matrix/identity/api/v1"));
306
307 case 31:
308 isResponse = _context.sent;
309
310 if (!(!isResponse || !isResponse.raw || isResponse.action !== "SUCCESS")) {
311 _context.next = 35;
312 break;
313 }
314
315 logger.error("Invalid /api/v1 response");
316 return _context.abrupt("return", _bluebird2.default.resolve(failingClientConfig));
317
318 case 35:
319
320 // Step 6: Now that the identity server is valid, or never existed,
321 // populate the IS section.
322 if (isUrl && isUrl.length > 0) {
323 clientConfig["m.identity_server"] = {
324 state: AutoDiscovery.SUCCESS,
325 error: null,
326 base_url: isUrl
327 };
328 }
329
330 // Step 7: Copy any other keys directly into the clientConfig. This is for
331 // things like custom configuration of services.
332 (0, _keys2.default)(wellknown.raw).filter(function (k) {
333 return k !== "m.homeserver" && k !== "m.identity_server";
334 }).map(function (k) {
335 return clientConfig[k] = wellknown.raw[k];
336 });
337
338 // Step 8: Give the config to the caller (finally)
339 return _context.abrupt("return", _bluebird2.default.resolve(clientConfig));
340
341 case 38:
342 case "end":
343 return _context.stop();
344 }
345 }
346 }, _callee, this);
347 }));
348
349 function findClientConfig(_x) {
350 return _ref.apply(this, arguments);
351 }
352
353 return findClientConfig;
354 }()
355
356 /**
357 * Sanitizes a given URL to ensure it is either an HTTP or HTTP URL and
358 * is suitable for the requirements laid out by .well-known auto discovery.
359 * If valid, the URL will also be stripped of any trailing slashes.
360 * @param {string} url The potentially invalid URL to sanitize.
361 * @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
362 * @private
363 */
364
365 }, {
366 key: "_sanitizeWellKnownUrl",
367 value: function _sanitizeWellKnownUrl(url) {
368 if (!url) return false;
369
370 try {
371 // We have to try and parse the URL using the NodeJS URL
372 // library if we're on NodeJS and use the browser's URL
373 // library when we're in a browser. To accomplish this, we
374 // try the NodeJS version first and fall back to the browser.
375 var parsed = null;
376 try {
377 if (_url.URL) parsed = new _url.URL(url);else parsed = new URL(url);
378 } catch (e) {
379 parsed = new URL(url);
380 }
381
382 if (!parsed || !parsed.hostname) return false;
383 if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
384
385 var port = parsed.port ? ":" + parsed.port : "";
386 var path = parsed.pathname ? parsed.pathname : "";
387 var saferUrl = parsed.protocol + "//" + parsed.hostname + port + path;
388 if (saferUrl.endsWith("/")) {
389 saferUrl = saferUrl.substring(0, saferUrl.length - 1);
390 }
391 return saferUrl;
392 } catch (e) {
393 logger.error(e);
394 return false;
395 }
396 }
397
398 /**
399 * Fetches a JSON object from a given URL, as expected by all .well-known
400 * related lookups. If the server gives a 404 then the `action` will be
401 * IGNORE. If the server returns something that isn't JSON, the `action`
402 * will be FAIL_PROMPT. For any other failure the `action` will be FAIL_PROMPT.
403 *
404 * The returned object will be a result of the call in object form with
405 * the following properties:
406 * raw: The JSON object returned by the server.
407 * action: One of SUCCESS, IGNORE, or FAIL_PROMPT.
408 * reason: Relatively human readable description of what went wrong.
409 * error: The actual Error, if one exists.
410 * @param {string} url The URL to fetch a JSON object from.
411 * @return {Promise<object>} Resolves to the returned state.
412 * @private
413 */
414
415 }, {
416 key: "_fetchWellKnownObject",
417 value: function () {
418 var _ref2 = (0, _bluebird.method)(function (url) {
419 return new _bluebird2.default(function (resolve, reject) {
420 var request = require("./matrix").getRequest();
421 if (!request) throw new Error("No request library available");
422 request({ method: "GET", uri: url }, function (err, response, body) {
423 if (err || response.statusCode < 200 || response.statusCode >= 300) {
424 var action = "FAIL_PROMPT";
425 var reason = (err ? err.message : null) || "General failure";
426 if (response.statusCode === 404) {
427 action = "IGNORE";
428 reason = "No .well-known JSON file found";
429 }
430 resolve({ raw: {}, action: action, reason: reason, error: err });
431 return;
432 }
433
434 try {
435 resolve({ raw: JSON.parse(body), action: "SUCCESS" });
436 } catch (e) {
437 var _reason = "General failure";
438 if (e.name === "SyntaxError") _reason = "Invalid JSON";
439 resolve({
440 raw: {},
441 action: "FAIL_PROMPT",
442 reason: _reason, error: e
443 });
444 }
445 });
446 });
447 });
448
449 function _fetchWellKnownObject(_x2) {
450 return _ref2.apply(this, arguments);
451 }
452
453 return _fetchWellKnownObject;
454 }()
455 }, {
456 key: "FAIL_ERROR",
457
458 // Dev note: the constants defined here are related to but not
459 // exactly the same as those in the spec. This is to hopefully
460 // translate the meaning of the states in the spec, but also
461 // support our own if needed.
462
463 /**
464 * The auto discovery failed. The client is expected to communicate
465 * the error to the user and refuse logging in.
466 * @return {string}
467 * @constructor
468 */
469 get: function get() {
470 return "FAIL_ERROR";
471 }
472
473 /**
474 * The auto discovery failed, however the client may still recover
475 * from the problem. The client is recommended to that the same
476 * action it would for PROMPT while also warning the user about
477 * what went wrong. The client may also treat this the same as
478 * a FAIL_ERROR state.
479 * @return {string}
480 * @constructor
481 */
482
483 }, {
484 key: "FAIL_PROMPT",
485 get: function get() {
486 return "FAIL_PROMPT";
487 }
488
489 /**
490 * The auto discovery didn't fail but did not find anything of
491 * interest. The client is expected to prompt the user for more
492 * information, or fail if it prefers.
493 * @return {string}
494 * @constructor
495 */
496
497 }, {
498 key: "PROMPT",
499 get: function get() {
500 return "PROMPT";
501 }
502
503 /**
504 * The auto discovery was successful.
505 * @return {string}
506 * @constructor
507 */
508
509 }, {
510 key: "SUCCESS",
511 get: function get() {
512 return "SUCCESS";
513 }
514 }]);
515 return AutoDiscovery;
516}();
517//# sourceMappingURL=autodiscovery.js.map
\No newline at end of file