From f5125ac2b89f4f01286e7d168b394bbe344bc7aa Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 22 May 2025 11:24:53 +0100 Subject: [PATCH] Support build-time specified protocol scheme for oidc callback (#29814) * Support build-time specified protocol scheme for oidc callback Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 5 ++++ src/vector/platform/ElectronPlatform.tsx | 27 ++++++++++++------- .../vector/platform/ElectronPlatform-test.ts | 18 ++++++++++++- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 5a080e0d1c..f19a8591cb 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -130,6 +130,11 @@ declare global { interface Electron { on(channel: ElectronChannel, listener: (event: Event, ...args: any[]) => void): void; send(channel: ElectronChannel, ...args: any[]): void; + initialise(): Promise<{ + protocol: string; + sessionId: string; + config: IConfigOptions; + }>; } interface DesktopCapturerSource { diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index 3be158b18d..c8b544daf1 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -17,7 +17,6 @@ import { type OidcRegistrationClientMetadata, } from "matrix-js-sdk/src/matrix"; import React from "react"; -import { secureRandomString } from "matrix-js-sdk/src/randomstring"; import { logger } from "matrix-js-sdk/src/logger"; import BasePlatform, { UpdateCheckStatus, type UpdateStatus } from "../../BasePlatform"; @@ -97,8 +96,10 @@ function getUpdateCheckStatus(status: boolean | string): UpdateStatus { export default class ElectronPlatform extends BasePlatform { private readonly ipc = new IPCManager("ipcCall", "ipcReply"); private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager(); - // this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile - private readonly ssoID: string = secureRandomString(32); + private readonly initialised: Promise; + private protocol!: string; + private sessionId!: string; + private config!: IConfigOptions; public constructor() { super(); @@ -186,13 +187,21 @@ export default class ElectronPlatform extends BasePlatform { await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" }); }); - void this.ipc.call("startSSOFlow", this.ssoID); - BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); + + this.initialised = this.initialise(); + } + + private async initialise(): Promise { + const { protocol, sessionId, config } = await window.electron!.initialise(); + this.protocol = protocol; + this.sessionId = sessionId; + this.config = config; } public async getConfig(): Promise { - return this.ipc.call("getConfig"); + await this.initialised; + return this.config; } private onBreadcrumbsUpdate = (): void => { @@ -391,7 +400,7 @@ export default class ElectronPlatform extends BasePlatform { public getSSOCallbackUrl(fragmentAfterLogin?: string): URL { const url = super.getSSOCallbackUrl(fragmentAfterLogin); url.protocol = "element"; - url.searchParams.set(SSO_ID_KEY, this.ssoID); + url.searchParams.set(SSO_ID_KEY, this.sessionId); return url; } @@ -469,7 +478,7 @@ export default class ElectronPlatform extends BasePlatform { } public getOidcClientState(): string { - return `:${SSO_ID_KEY}:${this.ssoID}`; + return `:${SSO_ID_KEY}:${this.sessionId}`; } /** @@ -477,7 +486,7 @@ export default class ElectronPlatform extends BasePlatform { */ public getOidcCallbackUrl(): URL { const url = super.getOidcCallbackUrl(); - url.protocol = "io.element.desktop"; + url.protocol = this.protocol; // Trim the double slash into a single slash to comply with https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 if (url.href.startsWith(`${url.protocol}//`)) { url.href = url.href.replace("://", ":/"); diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts index 2633d15b14..c4fda4a20f 100644 --- a/test/unit-tests/vector/platform/ElectronPlatform-test.ts +++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts @@ -31,6 +31,11 @@ describe("ElectronPlatform", () => { const mockElectron = { on: jest.fn(), send: jest.fn(), + initialise: jest.fn().mockResolvedValue({ + protocol: "io.element.desktop", + sessionId: "session-id", + config: { _config: true }, + }), }; const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); @@ -64,6 +69,17 @@ describe("ElectronPlatform", () => { expect(rageshake.flush).toHaveBeenCalled(); }); + it("should load config", async () => { + const platform = new ElectronPlatform(); + await expect(platform.getConfig()).resolves.toEqual({ _config: true }); + }); + + it("should return oidc client state as expected", async () => { + const platform = new ElectronPlatform(); + await platform.getConfig(); + expect(platform.getOidcClientState()).toMatchInlineSnapshot(`":element-desktop-ssoid:session-id"`); + }); + it("dispatches view settings action on preferences event", () => { new ElectronPlatform(); const [event, handler] = getElectronEventHandlerCall("preferences")!; @@ -287,7 +303,7 @@ describe("ElectronPlatform", () => { }); }); - describe("breacrumbs", () => { + describe("breadcrumbs", () => { it("should send breadcrumb updates over the IPC", () => { const spy = jest.spyOn(BreadcrumbsStore.instance, "on"); new ElectronPlatform();