mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
Fix OIDC redirect forwarded port handling (#1007)
This commit is contained in:
@@ -433,8 +433,8 @@ http {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
||||||
|
|
||||||
proxy_read_timeout 86400s;
|
proxy_read_timeout 86400s;
|
||||||
proxy_send_timeout 86400s;
|
proxy_send_timeout 86400s;
|
||||||
@@ -676,8 +676,8 @@ http {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
||||||
|
|
||||||
proxy_read_timeout 86400s;
|
proxy_read_timeout 86400s;
|
||||||
proxy_send_timeout 86400s;
|
proxy_send_timeout 86400s;
|
||||||
|
|||||||
+4
-4
@@ -434,8 +434,8 @@ http {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
||||||
|
|
||||||
proxy_read_timeout 86400s;
|
proxy_read_timeout 86400s;
|
||||||
proxy_send_timeout 86400s;
|
proxy_send_timeout 86400s;
|
||||||
@@ -677,8 +677,8 @@ http {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
||||||
|
|
||||||
proxy_read_timeout 86400s;
|
proxy_read_timeout 86400s;
|
||||||
proxy_send_timeout 86400s;
|
proxy_send_timeout 86400s;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
getRequestBasePath,
|
getRequestBasePath,
|
||||||
getRequestBaseUrl,
|
getRequestBaseUrl,
|
||||||
getRequestBaseUrlWithForceHTTPS,
|
getRequestBaseUrlWithForceHTTPS,
|
||||||
|
getRequestOrigin,
|
||||||
normalizeBasePath,
|
normalizeBasePath,
|
||||||
} from "./request-origin.js";
|
} from "./request-origin.js";
|
||||||
|
|
||||||
@@ -106,3 +107,52 @@ describe("getRequestBasePath", () => {
|
|||||||
).toBe("https://example.com/termix");
|
).toBe("https://example.com/termix");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getRequestOrigin", () => {
|
||||||
|
it("ignores non-numeric forwarded ports", () => {
|
||||||
|
expect(
|
||||||
|
getRequestOrigin(
|
||||||
|
request({
|
||||||
|
"x-forwarded-proto": "https",
|
||||||
|
"x-forwarded-host": "termix.test.de",
|
||||||
|
"x-forwarded-port": "{server_port}",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe("https://termix.test.de");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("drops invalid ports embedded in forwarded hosts", () => {
|
||||||
|
expect(
|
||||||
|
getRequestOrigin(
|
||||||
|
request({
|
||||||
|
"x-forwarded-proto": "https",
|
||||||
|
"x-forwarded-host": "termix.test.de:{server_port}",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe("https://termix.test.de");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps valid non-default forwarded ports", () => {
|
||||||
|
expect(
|
||||||
|
getRequestOrigin(
|
||||||
|
request({
|
||||||
|
"x-forwarded-proto": "https",
|
||||||
|
"x-forwarded-host": "termix.test.de",
|
||||||
|
"x-forwarded-port": "8443",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe("https://termix.test.de:8443");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits default forwarded ports", () => {
|
||||||
|
expect(
|
||||||
|
getRequestOrigin(
|
||||||
|
request({
|
||||||
|
"x-forwarded-proto": "https",
|
||||||
|
"x-forwarded-host": "termix.test.de",
|
||||||
|
"x-forwarded-port": "443",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe("https://termix.test.de");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,6 +7,44 @@ function firstHeaderValue(value: string | string[] | undefined): string {
|
|||||||
return raw.split(",")[0].trim();
|
return raw.split(",")[0].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePort(value: string | string[] | undefined): string {
|
||||||
|
const raw = firstHeaderValue(value);
|
||||||
|
if (!/^\d+$/.test(raw)) return "";
|
||||||
|
|
||||||
|
const port = Number(raw);
|
||||||
|
if (!Number.isInteger(port) || port < 1 || port > 65535) return "";
|
||||||
|
|
||||||
|
return String(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitHostHeader(value: string | string[] | undefined): {
|
||||||
|
host: string;
|
||||||
|
port: string;
|
||||||
|
} {
|
||||||
|
const raw = firstHeaderValue(value) || "localhost";
|
||||||
|
|
||||||
|
if (raw.startsWith("[")) {
|
||||||
|
const match = raw.match(/^(\[[^\]]+\])(?::(.+))?$/);
|
||||||
|
return {
|
||||||
|
host: match?.[1] || raw,
|
||||||
|
port: normalizePort(match?.[2]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = raw.split(":");
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return {
|
||||||
|
host: parts[0] || "localhost",
|
||||||
|
port: normalizePort(parts[1]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
host: raw,
|
||||||
|
port: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeBasePath(value: unknown): string {
|
export function normalizeBasePath(value: unknown): string {
|
||||||
if (typeof value !== "string") return "";
|
if (typeof value !== "string") return "";
|
||||||
let basePath = value.split(",")[0].trim();
|
let basePath = value.split(",")[0].trim();
|
||||||
@@ -45,38 +83,23 @@ export function getRequestOrigin(req: Request | IncomingMessage): string {
|
|||||||
: "http";
|
: "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
const portHeader = req.headers["x-forwarded-port"];
|
let port = normalizePort(req.headers["x-forwarded-port"]);
|
||||||
let port: string | undefined =
|
const { host, port: hostPort } = splitHostHeader(
|
||||||
typeof portHeader === "string"
|
req.headers["x-forwarded-host"] || req.headers.host,
|
||||||
? portHeader.split(",")[0].trim()
|
);
|
||||||
: undefined;
|
port ||= hostPort;
|
||||||
|
|
||||||
const hostHeaderRaw =
|
|
||||||
req.headers["x-forwarded-host"] || req.headers.host || "localhost";
|
|
||||||
const hostHeader =
|
|
||||||
typeof hostHeaderRaw === "string"
|
|
||||||
? hostHeaderRaw.split(",")[0].trim()
|
|
||||||
: String(hostHeaderRaw);
|
|
||||||
|
|
||||||
if (!port && hostHeader.includes(":")) {
|
|
||||||
const parts = hostHeader.split(":");
|
|
||||||
if (parts.length === 2 && !parts[0].includes("[")) {
|
|
||||||
port = parts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostWithoutPort = hostHeader.split(":")[0];
|
|
||||||
if (port) {
|
if (port) {
|
||||||
const isDefaultPort =
|
const isDefaultPort =
|
||||||
(protocol === "http" && port === "80") ||
|
(protocol === "http" && port === "80") ||
|
||||||
(protocol === "https" && port === "443");
|
(protocol === "https" && port === "443");
|
||||||
|
|
||||||
return isDefaultPort
|
return isDefaultPort
|
||||||
? `${protocol}://${hostWithoutPort}`
|
? `${protocol}://${host}`
|
||||||
: `${protocol}://${hostWithoutPort}:${port}`;
|
: `${protocol}://${host}:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${protocol}://${hostWithoutPort}`;
|
return `${protocol}://${host}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRequestOriginWithForceHTTPS(
|
export function getRequestOriginWithForceHTTPS(
|
||||||
|
|||||||
Reference in New Issue
Block a user