Fix Firefox desktop OIDC callback (#1044)

This commit is contained in:
ZacharyZcR
2026-07-13 23:00:22 +08:00
committed by GitHub
parent f2b7dae234
commit a3e615b59c
4 changed files with 74 additions and 8 deletions
+1 -1
View File
@@ -1397,7 +1397,7 @@ ipcMain.handle(
server.once("error", fail); server.once("error", fail);
server.listen(callbackPort, "127.0.0.1", async () => { server.listen(callbackPort, "localhost", async () => {
try { try {
await shell.openExternal(authUrl); await shell.openExternal(authUrl);
} catch (error) { } catch (error) {
+10 -7
View File
@@ -18,6 +18,10 @@ import {
getRequestBasePath, getRequestBasePath,
getRequestBaseUrlWithForceHTTPS, getRequestBaseUrlWithForceHTTPS,
} from "../../utils/request-origin.js"; } from "../../utils/request-origin.js";
import {
getDesktopOidcCallbackUrl,
isOidcTokenCallback,
} from "../../utils/oidc-desktop-callback.js";
import { deleteUserAndRelatedData } from "./delete-user-data.js"; import { deleteUserAndRelatedData } from "./delete-user-data.js";
import { import {
getOIDCConfigFromEnv, getOIDCConfigFromEnv,
@@ -654,7 +658,10 @@ router.get("/oidc/authorize", async (req, res) => {
const referer = req.get("Referer"); const referer = req.get("Referer");
let frontendOrigin; let frontendOrigin;
if (desktopCallbackPort) { if (desktopCallbackPort) {
frontendOrigin = `http://127.0.0.1:${desktopCallbackPort}/oidc-callback`; frontendOrigin = getDesktopOidcCallbackUrl(desktopCallbackPort);
if (!frontendOrigin) {
return res.status(400).json({ error: "Invalid desktop callback port" });
}
} else if (typeof appCallbackUrl === "string" && appCallbackUrl) { } else if (typeof appCallbackUrl === "string" && appCallbackUrl) {
let callbackUrl: URL; let callbackUrl: URL;
try { try {
@@ -1001,9 +1008,7 @@ router.get("/oidc/callback", async (req, res) => {
}); });
const ghRedirectUrl = new URL(frontendOrigin); const ghRedirectUrl = new URL(frontendOrigin);
ghRedirectUrl.searchParams.set("success", "true"); ghRedirectUrl.searchParams.set("success", "true");
const ghIsTokenCallback = const ghIsTokenCallback = isOidcTokenCallback(frontendOrigin);
frontendOrigin.startsWith("http://127.0.0.1:") ||
frontendOrigin.startsWith("termix-mobile:");
const ghMaxAge = const ghMaxAge =
deviceInfo.type === "desktop" || deviceInfo.type === "mobile" deviceInfo.type === "desktop" || deviceInfo.type === "mobile"
? 30 * 24 * 60 * 60 * 1000 ? 30 * 24 * 60 * 60 * 1000
@@ -1487,9 +1492,7 @@ router.get("/oidc/callback", async (req, res) => {
const redirectUrl = new URL(frontendOrigin); const redirectUrl = new URL(frontendOrigin);
redirectUrl.searchParams.set("success", "true"); redirectUrl.searchParams.set("success", "true");
const isTokenCallback = const isTokenCallback = isOidcTokenCallback(frontendOrigin);
frontendOrigin.startsWith("http://127.0.0.1:") ||
frontendOrigin.startsWith("termix-mobile:");
const maxAge = const maxAge =
deviceInfo.type === "desktop" || deviceInfo.type === "mobile" deviceInfo.type === "desktop" || deviceInfo.type === "mobile"
@@ -0,0 +1,38 @@
import { describe, expect, it } from "vitest";
import {
getDesktopOidcCallbackUrl,
isOidcTokenCallback,
} from "./oidc-desktop-callback";
describe("getDesktopOidcCallbackUrl", () => {
it("uses localhost so browsers do not upgrade the loopback callback", () => {
expect(getDesktopOidcCallbackUrl("17850")).toBe(
"http://localhost:17850/oidc-callback",
);
});
it.each(["", "0", "65536", "17850/path", ["17850"]])(
"rejects invalid callback port %j",
(port) => {
expect(getDesktopOidcCallbackUrl(port)).toBeNull();
},
);
});
describe("isOidcTokenCallback", () => {
it.each([
"http://localhost:17850/oidc-callback",
"http://127.0.0.1:17850/oidc-callback",
"termix-mobile://oidc-callback",
])("recognizes app callback %s", (url) => {
expect(isOidcTokenCallback(url)).toBe(true);
});
it.each([
"https://localhost:17850/oidc-callback",
"http://example.com:17850/oidc-callback",
"http://localhost:17850/other",
])("rejects non-app callback %s", (url) => {
expect(isOidcTokenCallback(url)).toBe(false);
});
});
@@ -0,0 +1,25 @@
const CALLBACK_PATH = "/oidc-callback";
export function getDesktopOidcCallbackUrl(value: unknown): string | null {
if (typeof value !== "string" || !/^\d+$/.test(value)) return null;
const port = Number(value);
if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
return `http://localhost:${port}${CALLBACK_PATH}`;
}
export function isOidcTokenCallback(value: string): boolean {
if (value.startsWith("termix-mobile:")) return true;
try {
const url = new URL(value);
return (
url.protocol === "http:" &&
(url.hostname === "localhost" || url.hostname === "127.0.0.1") &&
url.pathname === CALLBACK_PATH
);
} catch {
return false;
}
}