mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 04:43:36 +00:00
Fix Firefox desktop OIDC callback (#1044)
This commit is contained in:
+1
-1
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user