Skip to content

Support build-time specified protocol scheme for oidc callback #29814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 18 additions & 9 deletions src/vector/platform/ElectronPlatform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<void>;
private protocol!: string;
private sessionId!: string;
private config!: IConfigOptions;

public constructor() {
super();
Expand Down Expand Up @@ -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<void> {
const { protocol, sessionId, config } = await window.electron!.initialise();
this.protocol = protocol;
this.sessionId = sessionId;
this.config = config;
}

public async getConfig(): Promise<IConfigOptions | undefined> {
return this.ipc.call("getConfig");
await this.initialised;
return this.config;
}

private onBreadcrumbsUpdate = (): void => {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -469,15 +478,15 @@ export default class ElectronPlatform extends BasePlatform {
}

public getOidcClientState(): string {
return `:${SSO_ID_KEY}:${this.ssoID}`;
return `:${SSO_ID_KEY}:${this.sessionId}`;
}

/**
* The URL to return to after a successful OIDC authentication
*/
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("://", ":/");
Expand Down
18 changes: 17 additions & 1 deletion test/unit-tests/vector/platform/ElectronPlatform-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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")!;
Expand Down Expand Up @@ -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();
Expand Down
Loading