mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 01:05:42 +03:00
Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/repo-merge
# Conflicts: # package.json
This commit is contained in:
commit
643d13066c
7 changed files with 63 additions and 31 deletions
|
@ -1,3 +1,7 @@
|
||||||
|
Changes in [1.11.81](https://github.com/element-hq/element-web/releases/tag/v1.11.81) (2024-10-15)
|
||||||
|
==================================================================================================
|
||||||
|
This release fixes High severity vulnerability CVE-2024-47771 / GHSA-963w-49j9-gxj6
|
||||||
|
|
||||||
Changes in [1.11.80](https://github.com/element-hq/element-web/releases/tag/v1.11.80) (2024-10-08)
|
Changes in [1.11.80](https://github.com/element-hq/element-web/releases/tag/v1.11.80) (2024-10-08)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "element-web",
|
"name": "element-web",
|
||||||
"version": "1.11.80",
|
"version": "1.11.81",
|
||||||
"description": "A feature-rich client for Matrix.org",
|
"description": "A feature-rich client for Matrix.org",
|
||||||
"author": "New Vector Ltd.",
|
"author": "New Vector Ltd.",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
"jwt-decode": "4.0.0",
|
"jwt-decode": "4.0.0",
|
||||||
"@vector-im/compound-design-tokens": "1.8.0",
|
"@vector-im/compound-design-tokens": "1.8.0",
|
||||||
"@vector-im/compound-web": "7.0.0",
|
"@vector-im/compound-web": "7.0.0",
|
||||||
"@floating-ui/react": "0.26.11",
|
"@floating-ui/react": "0.26.24",
|
||||||
"@radix-ui/react-id": "1.1.0",
|
"@radix-ui/react-id": "1.1.0",
|
||||||
"caniuse-lite": "1.0.30001655",
|
"caniuse-lite": "1.0.30001655",
|
||||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||||
|
|
1
src/@types/global.d.ts
vendored
1
src/@types/global.d.ts
vendored
|
@ -64,6 +64,7 @@ type ElectronChannel =
|
||||||
| "userDownloadAction"
|
| "userDownloadAction"
|
||||||
| "openDesktopCapturerSourcePicker"
|
| "openDesktopCapturerSourcePicker"
|
||||||
| "userAccessToken"
|
| "userAccessToken"
|
||||||
|
| "homeserverUrl"
|
||||||
| "serverSupportedVersions";
|
| "serverSupportedVersions";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
|
@ -40,12 +40,15 @@ global.addEventListener("fetch", (event: FetchEvent) => {
|
||||||
|
|
||||||
// Note: ideally we'd keep the request headers etc, but in practice we can't even see those details.
|
// Note: ideally we'd keep the request headers etc, but in practice we can't even see those details.
|
||||||
// See https://stackoverflow.com/a/59152482
|
// See https://stackoverflow.com/a/59152482
|
||||||
let url = event.request.url;
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
// We only intercept v3 download and thumbnail requests as presumably everything else is deliberate.
|
// We only intercept v3 download and thumbnail requests as presumably everything else is deliberate.
|
||||||
// For example, `/_matrix/media/unstable` or `/_matrix/media/v3/preview_url` are something well within
|
// For example, `/_matrix/media/unstable` or `/_matrix/media/v3/preview_url` are something well within
|
||||||
// the control of the application, and appear to be choices made at a higher level than us.
|
// the control of the application, and appear to be choices made at a higher level than us.
|
||||||
if (!url.includes("/_matrix/media/v3/download") && !url.includes("/_matrix/media/v3/thumbnail")) {
|
if (
|
||||||
|
!url.pathname.startsWith("/_matrix/media/v3/download") &&
|
||||||
|
!url.pathname.startsWith("/_matrix/media/v3/thumbnail")
|
||||||
|
) {
|
||||||
return; // not a URL we care about
|
return; // not a URL we care about
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,34 +56,42 @@ global.addEventListener("fetch", (event: FetchEvent) => {
|
||||||
// later on we need to proxy the request through if it turns out the server doesn't support authentication.
|
// later on we need to proxy the request through if it turns out the server doesn't support authentication.
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
(async (): Promise<Response> => {
|
(async (): Promise<Response> => {
|
||||||
let accessToken: string | undefined;
|
let auth: { accessToken?: string; homeserver: string } | undefined;
|
||||||
try {
|
try {
|
||||||
// Figure out which homeserver we're communicating with
|
// Figure out which homeserver we're communicating with
|
||||||
const csApi = url.substring(0, url.indexOf("/_matrix/media/v3"));
|
const csApi = url.origin;
|
||||||
|
|
||||||
// Add jitter to reduce request spam, particularly to `/versions` on initial page load
|
// Add jitter to reduce request spam, particularly to `/versions` on initial page load
|
||||||
await new Promise<void>((resolve) => setTimeout(() => resolve(), Math.random() * 10));
|
await new Promise<void>((resolve) => setTimeout(() => resolve(), Math.random() * 10));
|
||||||
|
|
||||||
// Locate our access token, and populate the fetchConfig with the authentication header.
|
// Locate the access token and homeserver url
|
||||||
// @ts-expect-error - service worker types are not available. See 'fetch' event handler.
|
// @ts-expect-error - service worker types are not available. See 'fetch' event handler.
|
||||||
const client = await global.clients.get(event.clientId);
|
const client = await global.clients.get(event.clientId);
|
||||||
accessToken = await getAccessToken(client);
|
auth = await getAuthData(client);
|
||||||
|
|
||||||
|
// Is this request actually going to the homeserver?
|
||||||
|
const isRequestToHomeServer = url.origin === new URL(auth.homeserver).origin;
|
||||||
|
if (!isRequestToHomeServer) {
|
||||||
|
throw new Error("Request appears to be for media endpoint but wrong homeserver!");
|
||||||
|
}
|
||||||
|
|
||||||
// Update or populate the server support map using a (usually) authenticated `/versions` call.
|
// Update or populate the server support map using a (usually) authenticated `/versions` call.
|
||||||
await tryUpdateServerSupportMap(csApi, accessToken);
|
await tryUpdateServerSupportMap(csApi, auth.accessToken);
|
||||||
|
|
||||||
// If we have server support (and a means of authentication), rewrite the URL to use MSC3916 endpoints.
|
// If we have server support (and a means of authentication), rewrite the URL to use MSC3916 endpoints.
|
||||||
if (serverSupportMap[csApi].supportsAuthedMedia && accessToken) {
|
if (serverSupportMap[csApi].supportsAuthedMedia && auth.accessToken) {
|
||||||
url = url.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
|
url.href = url.href.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
|
||||||
} // else by default we make no changes
|
} // else by default we make no changes
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// In case of some error, we stay safe by not adding the access-token to the request.
|
||||||
|
auth = undefined;
|
||||||
console.error("SW: Error in request rewrite.", err);
|
console.error("SW: Error in request rewrite.", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add authentication and send the request. We add authentication even if MSC3916 endpoints aren't
|
// Add authentication and send the request. We add authentication even if MSC3916 endpoints aren't
|
||||||
// being used to ensure patches like this work:
|
// being used to ensure patches like this work:
|
||||||
// https://github.com/matrix-org/synapse/commit/2390b66bf0ec3ff5ffb0c7333f3c9b239eeb92bb
|
// https://github.com/matrix-org/synapse/commit/2390b66bf0ec3ff5ffb0c7333f3c9b239eeb92bb
|
||||||
return fetch(url, fetchConfigForToken(accessToken));
|
return fetch(url, fetchConfigForToken(auth?.accessToken));
|
||||||
})(),
|
})(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -106,35 +117,36 @@ async function tryUpdateServerSupportMap(clientApiUrl: string, accessToken?: str
|
||||||
|
|
||||||
// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
|
// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
|
||||||
// unknown for now and force-cast it to something close enough later.
|
// unknown for now and force-cast it to something close enough later.
|
||||||
async function getAccessToken(client: unknown): Promise<string | undefined> {
|
async function getAuthData(client: unknown): Promise<{ accessToken: string; homeserver: string }> {
|
||||||
// Access tokens are encrypted at rest, so while we can grab the "access token", we'll need to do work to get the
|
// Access tokens are encrypted at rest, so while we can grab the "access token", we'll need to do work to get the
|
||||||
// real thing.
|
// real thing.
|
||||||
const encryptedAccessToken = await idbLoad("account", "mx_access_token");
|
const encryptedAccessToken = await idbLoad("account", "mx_access_token");
|
||||||
|
|
||||||
// We need to extract a user ID and device ID from localstorage, which means calling WebPlatform for the
|
// We need to extract a user ID and device ID from localstorage, which means calling WebPlatform for the
|
||||||
// read operation. Service workers can't access localstorage.
|
// read operation. Service workers can't access localstorage.
|
||||||
const { userId, deviceId } = await askClientForUserIdParams(client);
|
const { userId, deviceId, homeserver } = await askClientForUserIdParams(client);
|
||||||
|
|
||||||
// ... and this is why we need the user ID and device ID: they're index keys for the pickle key table.
|
// ... and this is why we need the user ID and device ID: they're index keys for the pickle key table.
|
||||||
const pickleKeyData = await idbLoad("pickleKey", [userId, deviceId]);
|
const pickleKeyData = await idbLoad("pickleKey", [userId, deviceId]);
|
||||||
if (pickleKeyData && (!pickleKeyData.encrypted || !pickleKeyData.iv || !pickleKeyData.cryptoKey)) {
|
if (pickleKeyData && (!pickleKeyData.encrypted || !pickleKeyData.iv || !pickleKeyData.cryptoKey)) {
|
||||||
console.error("SW: Invalid pickle key loaded - ignoring");
|
throw new Error("SW: Invalid pickle key loaded - ignoring");
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, try decrypting the thing and return that. This may fail, but that's okay.
|
// Finally, try decrypting the thing and return that. This may fail, but that's okay.
|
||||||
try {
|
try {
|
||||||
const pickleKey = await buildAndEncodePickleKey(pickleKeyData, userId, deviceId);
|
const pickleKey = await buildAndEncodePickleKey(pickleKeyData, userId, deviceId);
|
||||||
return tryDecryptToken(pickleKey, encryptedAccessToken, ACCESS_TOKEN_IV);
|
const accessToken = await tryDecryptToken(pickleKey, encryptedAccessToken, ACCESS_TOKEN_IV);
|
||||||
|
return { accessToken, homeserver };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("SW: Error decrypting access token.", e);
|
throw new Error("SW: Error decrypting access token.", { cause: e });
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
|
// Ideally we'd use the `Client` interface for `client`, but since it's not available (see 'fetch' listener), we use
|
||||||
// unknown for now and force-cast it to something close enough inside the function.
|
// unknown for now and force-cast it to something close enough inside the function.
|
||||||
async function askClientForUserIdParams(client: unknown): Promise<{ userId: string; deviceId: string }> {
|
async function askClientForUserIdParams(
|
||||||
|
client: unknown,
|
||||||
|
): Promise<{ userId: string; deviceId: string; homeserver: string }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Dev note: this uses postMessage, which is a highly insecure channel. postMessage is typically visible to other
|
// Dev note: this uses postMessage, which is a highly insecure channel. postMessage is typically visible to other
|
||||||
// tabs, windows, browser extensions, etc, making it far from ideal for sharing sensitive information. This is
|
// tabs, windows, browser extensions, etc, making it far from ideal for sharing sensitive information. This is
|
||||||
|
|
|
@ -123,6 +123,11 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||||
window.electron!.send("userAccessToken", MatrixClientPeg.get()?.getAccessToken());
|
window.electron!.send("userAccessToken", MatrixClientPeg.get()?.getAccessToken());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// `homeserverUrl` (IPC) is requested by the main process. A reply is sent over the same channel.
|
||||||
|
window.electron.on("homeserverUrl", () => {
|
||||||
|
window.electron!.send("homeserverUrl", MatrixClientPeg.get()?.getHomeserverUrl());
|
||||||
|
});
|
||||||
|
|
||||||
// `serverSupportedVersions` is requested by the main process when it needs to know if the
|
// `serverSupportedVersions` is requested by the main process when it needs to know if the
|
||||||
// server supports a particular version. This is primarily used to detect authenticated media
|
// server supports a particular version. This is primarily used to detect authenticated media
|
||||||
// support. A reply is sent over the same channel.
|
// support. A reply is sent over the same channel.
|
||||||
|
|
|
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||||
|
|
||||||
import UAParser from "ua-parser-js";
|
import UAParser from "ua-parser-js";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||||
|
|
||||||
import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
@ -62,10 +63,12 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
if (event.data?.["type"] === "userinfo" && event.data?.["responseKey"]) {
|
if (event.data?.["type"] === "userinfo" && event.data?.["responseKey"]) {
|
||||||
const userId = localStorage.getItem("mx_user_id");
|
const userId = localStorage.getItem("mx_user_id");
|
||||||
const deviceId = localStorage.getItem("mx_device_id");
|
const deviceId = localStorage.getItem("mx_device_id");
|
||||||
|
const homeserver = MatrixClientPeg.get()?.getHomeserverUrl();
|
||||||
event.source!.postMessage({
|
event.source!.postMessage({
|
||||||
responseKey: event.data["responseKey"],
|
responseKey: event.data["responseKey"],
|
||||||
userId,
|
userId,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
homeserver,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
29
yarn.lock
29
yarn.lock
|
@ -1683,25 +1683,32 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@floating-ui/dom" "^1.0.0"
|
"@floating-ui/dom" "^1.0.0"
|
||||||
|
|
||||||
"@floating-ui/react@0.26.11", "@floating-ui/react@^0.26.24":
|
"@floating-ui/react-dom@^2.1.2":
|
||||||
version "0.26.11"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.11.tgz#226d3fec890de439443b62f3138ef7de052b0998"
|
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
|
||||||
integrity sha512-fo01Cu+jzLDVG/AYAV2OtV6flhXvxP5rDaR1Fk8WWhtsFqwk478Dr2HGtB8s0HqQCsFWVbdHYpPjMiQiR/A9VA==
|
integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@floating-ui/react-dom" "^2.0.0"
|
"@floating-ui/dom" "^1.0.0"
|
||||||
"@floating-ui/utils" "^0.2.0"
|
|
||||||
tabbable "^6.0.0"
|
|
||||||
|
|
||||||
"@floating-ui/utils@^0.2.0":
|
"@floating-ui/react@0.26.24", "@floating-ui/react@^0.26.24":
|
||||||
version "0.2.5"
|
version "0.26.24"
|
||||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9"
|
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.24.tgz#072b9dfeca4e79ef4e3000ef1c28e0ffc86f4ed4"
|
||||||
integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==
|
integrity sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==
|
||||||
|
dependencies:
|
||||||
|
"@floating-ui/react-dom" "^2.1.2"
|
||||||
|
"@floating-ui/utils" "^0.2.8"
|
||||||
|
tabbable "^6.0.0"
|
||||||
|
|
||||||
"@floating-ui/utils@^0.2.4":
|
"@floating-ui/utils@^0.2.4":
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c"
|
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c"
|
||||||
integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==
|
integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==
|
||||||
|
|
||||||
|
"@floating-ui/utils@^0.2.8":
|
||||||
|
version "0.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
|
||||||
|
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@2.0.0":
|
"@formatjs/ecma402-abstract@2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17"
|
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17"
|
||||||
|
|
Loading…
Reference in a new issue