diff --git a/src/ui/lib/embedded-frame.ts b/src/ui/lib/embedded-frame.ts index af6879fbe..e21bd5156 100644 --- a/src/ui/lib/embedded-frame.ts +++ b/src/ui/lib/embedded-frame.ts @@ -16,14 +16,20 @@ export function isInMobileWebView(): boolean { ); } +// The desktop app's own user agent. The link window frames the server +// cross-origin, and Electron does not carry webContents.setUserAgent into +// such a frame, so there navigator.userAgent is Electron's default, which +// still names the app: "... termix/26.10.2 Chrome/... Electron/43.7.7 ...". +const DESKTOP_USER_AGENT = /Termix-Desktop\/|\btermix\/[\w.-]+ .*\bElectron\//i; + // Being framed alone is not enough: any site could frame this page and -// receive the token posted to window.parent. The desktop app tags every -// request and frame with its own user agent, which a web page cannot fake -// in someone else's browser. +// receive the token posted to window.parent. The desktop app's user agent, +// in either form above, is something a web page cannot fake in someone +// else's browser. export function isInElectronWebView(): boolean { if (isInMobileWebView()) return false; if ((window as EmbeddedFrameWindow).IS_ELECTRON_WEBVIEW) return true; - if (!/Termix-Desktop\//.test(navigator.userAgent)) return false; + if (!DESKTOP_USER_AGENT.test(navigator.userAgent)) return false; try { if (window.self !== window.top) return true; } catch { diff --git a/src/ui/tests/lib/embedded-frame.test.ts b/src/ui/tests/lib/embedded-frame.test.ts index fb696af67..c0f80e6d5 100644 --- a/src/ui/tests/lib/embedded-frame.test.ts +++ b/src/ui/tests/lib/embedded-frame.test.ts @@ -49,4 +49,26 @@ describe("embedded frame detection", () => { ); expect(isInElectronWebView()).toBe(true); }); + + it("spots the desktop link window by Electron's default user agent", () => { + // What a cross-origin frame inside the desktop app reports (#1675). + vi.spyOn(window, "top", "get").mockReturnValue({} as Window); + vi.spyOn(navigator, "userAgent", "get").mockReturnValue( + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) termix/26.10.2 Chrome/150.0.7871.250 Electron/43.7.7 Safari/537.36", + ); + expect(isInElectronWebView()).toBe(true); + }); + + it("does not trust another Electron app or a plain browser frame", () => { + vi.spyOn(window, "top", "get").mockReturnValue({} as Window); + const ua = vi.spyOn(navigator, "userAgent", "get"); + ua.mockReturnValue( + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0 Safari/537.36", + ); + expect(isInElectronWebView()).toBe(false); + ua.mockReturnValue( + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) other-app/1.0.0 Chrome/150.0 Electron/43.7.7 Safari/537.36", + ); + expect(isInElectronWebView()).toBe(false); + }); });