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>
This commit is contained in:
committed by
GitHub
parent
bd142412e5
commit
f5125ac2b8
5
src/@types/global.d.ts
vendored
5
src/@types/global.d.ts
vendored
@@ -130,6 +130,11 @@ declare global {
|
|||||||
interface Electron {
|
interface Electron {
|
||||||
on(channel: ElectronChannel, listener: (event: Event, ...args: any[]) => void): void;
|
on(channel: ElectronChannel, listener: (event: Event, ...args: any[]) => void): void;
|
||||||
send(channel: ElectronChannel, ...args: any[]): void;
|
send(channel: ElectronChannel, ...args: any[]): void;
|
||||||
|
initialise(): Promise<{
|
||||||
|
protocol: string;
|
||||||
|
sessionId: string;
|
||||||
|
config: IConfigOptions;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DesktopCapturerSource {
|
interface DesktopCapturerSource {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
type OidcRegistrationClientMetadata,
|
type OidcRegistrationClientMetadata,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import BasePlatform, { UpdateCheckStatus, type UpdateStatus } from "../../BasePlatform";
|
import BasePlatform, { UpdateCheckStatus, type UpdateStatus } from "../../BasePlatform";
|
||||||
@@ -97,8 +96,10 @@ function getUpdateCheckStatus(status: boolean | string): UpdateStatus {
|
|||||||
export default class ElectronPlatform extends BasePlatform {
|
export default class ElectronPlatform extends BasePlatform {
|
||||||
private readonly ipc = new IPCManager("ipcCall", "ipcReply");
|
private readonly ipc = new IPCManager("ipcCall", "ipcReply");
|
||||||
private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
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 initialised: Promise<void>;
|
||||||
private readonly ssoID: string = secureRandomString(32);
|
private protocol!: string;
|
||||||
|
private sessionId!: string;
|
||||||
|
private config!: IConfigOptions;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -186,13 +187,21 @@ export default class ElectronPlatform extends BasePlatform {
|
|||||||
await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" });
|
await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" });
|
||||||
});
|
});
|
||||||
|
|
||||||
void this.ipc.call("startSSOFlow", this.ssoID);
|
|
||||||
|
|
||||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
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> {
|
public async getConfig(): Promise<IConfigOptions | undefined> {
|
||||||
return this.ipc.call("getConfig");
|
await this.initialised;
|
||||||
|
return this.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onBreadcrumbsUpdate = (): void => {
|
private onBreadcrumbsUpdate = (): void => {
|
||||||
@@ -391,7 +400,7 @@ export default class ElectronPlatform extends BasePlatform {
|
|||||||
public getSSOCallbackUrl(fragmentAfterLogin?: string): URL {
|
public getSSOCallbackUrl(fragmentAfterLogin?: string): URL {
|
||||||
const url = super.getSSOCallbackUrl(fragmentAfterLogin);
|
const url = super.getSSOCallbackUrl(fragmentAfterLogin);
|
||||||
url.protocol = "element";
|
url.protocol = "element";
|
||||||
url.searchParams.set(SSO_ID_KEY, this.ssoID);
|
url.searchParams.set(SSO_ID_KEY, this.sessionId);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +478,7 @@ export default class ElectronPlatform extends BasePlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getOidcClientState(): string {
|
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 {
|
public getOidcCallbackUrl(): URL {
|
||||||
const url = super.getOidcCallbackUrl();
|
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
|
// 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}//`)) {
|
if (url.href.startsWith(`${url.protocol}//`)) {
|
||||||
url.href = url.href.replace("://", ":/");
|
url.href = url.href.replace("://", ":/");
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ describe("ElectronPlatform", () => {
|
|||||||
const mockElectron = {
|
const mockElectron = {
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
send: 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");
|
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||||
@@ -64,6 +69,17 @@ describe("ElectronPlatform", () => {
|
|||||||
expect(rageshake.flush).toHaveBeenCalled();
|
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", () => {
|
it("dispatches view settings action on preferences event", () => {
|
||||||
new ElectronPlatform();
|
new ElectronPlatform();
|
||||||
const [event, handler] = getElectronEventHandlerCall("preferences")!;
|
const [event, handler] = getElectronEventHandlerCall("preferences")!;
|
||||||
@@ -287,7 +303,7 @@ describe("ElectronPlatform", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("breacrumbs", () => {
|
describe("breadcrumbs", () => {
|
||||||
it("should send breadcrumb updates over the IPC", () => {
|
it("should send breadcrumb updates over the IPC", () => {
|
||||||
const spy = jest.spyOn(BreadcrumbsStore.instance, "on");
|
const spy = jest.spyOn(BreadcrumbsStore.instance, "on");
|
||||||
new ElectronPlatform();
|
new ElectronPlatform();
|
||||||
|
|||||||
Reference in New Issue
Block a user