Skip to content

Commit

Permalink
Merge commit from fork
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MidhunSureshR authored and dbkr committed Oct 15, 2024
1 parent 6d56284 commit 6c78684
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 23 deletions.
81 changes: 58 additions & 23 deletions src/media-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
}
1 change: 1 addition & 0 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const CHANNELS = [
"userDownloadAction",
"openDesktopCapturerSourcePicker",
"userAccessToken",
"homeserverUrl",
"serverSupportedVersions",
];

Expand Down

0 comments on commit 6c78684

Please sign in to comment.