Merge commit from fork

* Check url with homeserver

* Move check to where access-token is added

* Do IPC comm sparingly

Before, the code would fetch the hs for every request.
Since this needs the whole event-handler dance, it's best we do it only
for the requests that match the media endpoints.

Also added some try..catch since we create URL objects that could
potentially throw

* Check origin instead of just hostname
This commit is contained in:
R Midhun Suresh 2024-10-15 17:21:06 +05:30 committed by GitHub
parent 60d28ca3d8
commit 2d6e087fb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 23 deletions

View File

@ -33,39 +33,74 @@ async function getAccessToken(window: BrowserWindow): Promise<string | undefined
});
}
/**
* Get the homeserver url
* This requires asking the renderer process for the homeserver url.
*/
async function getHomeserverUrl(window: BrowserWindow): Promise<string> {
return new Promise((resolve) => {
ipcMain.once("homeserverUrl", (_, homeserver) => {
resolve(homeserver);
});
window.webContents.send("homeserverUrl"); // ping now that the listener exists
});
}
export function setupMediaAuth(window: BrowserWindow): void {
session.defaultSession.webRequest.onBeforeRequest(async (req, callback) => {
// This handler emulates the element-web service worker, where URLs are rewritten late in the request
// for backwards compatibility. As authenticated media becomes more prevalent, this should be replaced
// by the app using authenticated URLs from the outset.
let url = req.url;
if (!url.includes("/_matrix/media/v3/download") && !url.includes("/_matrix/media/v3/thumbnail")) {
return callback({}); // not a URL we care about
}
try {
const url = new URL(req.url);
if (
!url.pathname.startsWith("/_matrix/media/v3/download") &&
!url.pathname.startsWith("/_matrix/media/v3/thumbnail")
) {
return callback({}); // not a URL we care about
}
const supportedVersions = await getSupportedVersions(window);
// We have to check that the access token is truthy otherwise we'd be intercepting pre-login media request too,
// e.g. those required for SSO button icons.
const accessToken = await getAccessToken(window);
if (supportedVersions.includes("v1.11") && accessToken) {
url = url.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
return callback({ redirectURL: url });
} else {
return callback({}); // no support == no modification
const supportedVersions = await getSupportedVersions(window);
// We have to check that the access token is truthy otherwise we'd be intercepting pre-login media request too,
// e.g. those required for SSO button icons.
const accessToken = await getAccessToken(window);
if (supportedVersions.includes("v1.11") && accessToken) {
url.href = url.href.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
return callback({ redirectURL: url.toString() });
} else {
return callback({}); // no support == no modification
}
} catch (e) {
console.error(e);
}
});
session.defaultSession.webRequest.onBeforeSendHeaders(async (req, callback) => {
if (!req.url.includes("/_matrix/client/v1/media")) {
return callback({}); // invoke unmodified
}
try {
const url = new URL(req.url);
if (!url.pathname.startsWith("/_matrix/client/v1/media")) {
return callback({}); // invoke unmodified
}
// Only add authorization header to authenticated media URLs. This emulates the service worker
// behaviour in element-web.
const accessToken = await getAccessToken(window);
// `accessToken` can be falsy, but if we're trying to download media without authentication
// then we should expect failure anyway.
const headers = { ...req.requestHeaders, Authorization: `Bearer ${accessToken}` };
return callback({ requestHeaders: headers });
// Is this request actually going to the homeserver?
// We don't combine this check with the one above on purpose.
// We're fetching the homeserver url through IPC and should do so
// as sparingly as possible.
const homeserver = await getHomeserverUrl(window);
const isRequestToHomeServer = homeserver && url.origin === new URL(homeserver).origin;
if (!isRequestToHomeServer) {
return callback({}); // invoke unmodified
}
// Only add authorization header to authenticated media URLs. This emulates the service worker
// behaviour in element-web.
const accessToken = await getAccessToken(window);
// `accessToken` can be falsy, but if we're trying to download media without authentication
// then we should expect failure anyway.
const headers = { ...req.requestHeaders, Authorization: `Bearer ${accessToken}` };
return callback({ requestHeaders: headers });
} catch (e) {
console.error(e);
}
});
}

View File

@ -28,6 +28,7 @@ const CHANNELS = [
"userDownloadAction",
"openDesktopCapturerSourcePicker",
"userAccessToken",
"homeserverUrl",
"serverSupportedVersions",
];