allow setting a web instance in the url from a whitelist

as web clients can be hosted on multiple domains, allow specifying
which hosted instance we want to use.

as this could be phishing vector, we work with a host whitelist for now
until a better solution comes to mind.
This commit is contained in:
Bruno Windels 2021-02-01 11:12:25 +01:00
parent 0aa9c2e766
commit 47d0db89c1
2 changed files with 39 additions and 7 deletions

View file

@ -40,6 +40,21 @@ function asPrefix(identifierKind) {
} }
} }
function getWebInstanceMap(queryParams) {
const prefix = "web-instance[";
const postfix = "]";
const webInstanceParams = queryParams.filter(([key]) => key.startsWith(prefix) && key.endsWith(postfix));
const webInstances = webInstanceParams.map(([key, value]) => {
const noPrefix = key.substr(prefix.length);
const clientId = noPrefix.substr(0, noPrefix.length - postfix.length);
return [clientId, value];
});
return webInstances.reduce((map, [clientId, host]) => {
map[clientId] = host;
return map;
}, {});
}
export function getLabelForLinkKind(kind) { export function getLabelForLinkKind(kind) {
switch (kind) { switch (kind) {
case LinkKind.User: return "Start chat"; case LinkKind.User: return "Start chat";
@ -74,8 +89,12 @@ export class Link {
let viaServers = []; let viaServers = [];
let clientId = null; let clientId = null;
let webInstances = {};
if (queryParamsStr) { if (queryParamsStr) {
const queryParams = queryParamsStr.split("&").map(pair => pair.split("=")); const queryParams = queryParamsStr.split("&").map(pair => {
const [key, value] = pair.split("=");
return [decodeURIComponent(key), decodeURIComponent(value)];
});
viaServers = queryParams viaServers = queryParams
.filter(([key, value]) => key === "via") .filter(([key, value]) => key === "via")
.map(([,value]) => value); .map(([,value]) => value);
@ -83,6 +102,7 @@ export class Link {
if (clientParam) { if (clientParam) {
clientId = clientParam[1]; clientId = clientParam[1];
} }
webInstances = getWebInstanceMap(queryParams);
} }
if (linkStr.startsWith("#/")) { if (linkStr.startsWith("#/")) {
@ -96,32 +116,33 @@ export class Link {
if (matches) { if (matches) {
const server = matches[2]; const server = matches[2];
const localPart = matches[1]; const localPart = matches[1];
return new Link(clientId, viaServers, IdentifierKind.UserId, localPart, server); return new Link(clientId, viaServers, IdentifierKind.UserId, localPart, server, webInstances);
} }
matches = ROOMALIAS_PATTERN.exec(identifier); matches = ROOMALIAS_PATTERN.exec(identifier);
if (matches) { if (matches) {
const server = matches[2]; const server = matches[2];
const localPart = matches[1]; const localPart = matches[1];
return new Link(clientId, viaServers, IdentifierKind.RoomAlias, localPart, server, eventId); return new Link(clientId, viaServers, IdentifierKind.RoomAlias, localPart, server, webInstances, eventId);
} }
matches = ROOMID_PATTERN.exec(identifier); matches = ROOMID_PATTERN.exec(identifier);
if (matches) { if (matches) {
const server = matches[2]; const server = matches[2];
const localPart = matches[1]; const localPart = matches[1];
return new Link(clientId, viaServers, IdentifierKind.RoomId, localPart, server, eventId); return new Link(clientId, viaServers, IdentifierKind.RoomId, localPart, server, webInstances, eventId);
} }
matches = GROUPID_PATTERN.exec(identifier); matches = GROUPID_PATTERN.exec(identifier);
if (matches) { if (matches) {
const server = matches[2]; const server = matches[2];
const localPart = matches[1]; const localPart = matches[1];
return new Link(clientId, viaServers, IdentifierKind.GroupId, localPart, server); return new Link(clientId, viaServers, IdentifierKind.GroupId, localPart, server, webInstances);
} }
return null; return null;
} }
constructor(clientId, viaServers, identifierKind, localPart, server, eventId) { constructor(clientId, viaServers, identifierKind, localPart, server, webInstances, eventId) {
const servers = [server]; const servers = [server];
servers.push(...viaServers); servers.push(...viaServers);
this.webInstances = webInstances;
this.servers = orderedUnique(servers); this.servers = orderedUnique(servers);
this.identifierKind = identifierKind; this.identifierKind = identifierKind;
this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`; this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`;

View file

@ -17,6 +17,13 @@ limitations under the License.
import {Maturity, Platform, LinkKind, import {Maturity, Platform, LinkKind,
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js"; FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
const trustedWebInstances = [
"app.element.io", // first one is the default one
"develop.element.io",
"chat.fosdem.org",
"chat.mozilla.org",
];
/** /**
* Information on how to deep link to a given matrix client. * Information on how to deep link to a given matrix client.
*/ */
@ -56,7 +63,11 @@ export class Element {
break; break;
} }
if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb || platform === Platform.iOS) { if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb || platform === Platform.iOS) {
return `https://app.element.io/#/${fragmentPath}`; let instanceHost = trustedWebInstances[0];
if (trustedWebInstances.includes(link.webInstances[this.id])) {
instanceHost = link.webInstances[this.id];
}
return `https://${instanceHost}/#/${fragmentPath}`;
} else if (platform === Platform.Linux || platform === Platform.Windows || platform === Platform.macOS) { } else if (platform === Platform.Linux || platform === Platform.Windows || platform === Platform.macOS) {
return `element://vector/webapp/#/${fragmentPath}`; return `element://vector/webapp/#/${fragmentPath}`;
} else { } else {