mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
fix: monitoring defaults not saving and OIDC redirect issues
This commit is contained in:
@@ -100,6 +100,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 $proxy_x_forwarded_port;
|
||||||
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/version(/.*)?$ {
|
location ~ ^/version(/.*)?$ {
|
||||||
|
|||||||
@@ -89,6 +89,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 $proxy_x_forwarded_port;
|
||||||
|
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/version(/.*)?$ {
|
location ~ ^/version(/.*)?$ {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
generateDeviceFingerprint,
|
generateDeviceFingerprint,
|
||||||
} from "../../utils/user-agent-parser.js";
|
} from "../../utils/user-agent-parser.js";
|
||||||
import { loginRateLimiter } from "../../utils/login-rate-limiter.js";
|
import { loginRateLimiter } from "../../utils/login-rate-limiter.js";
|
||||||
|
import { getRequestOriginWithForceHTTPS } from "../../utils/request-origin.js";
|
||||||
|
|
||||||
const authManager = AuthManager.getInstance();
|
const authManager = AuthManager.getInstance();
|
||||||
|
|
||||||
@@ -798,6 +799,9 @@ router.get("/oidc-config/admin", requireAdmin, async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
router.get("/oidc/authorize", async (req, res) => {
|
router.get("/oidc/authorize", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const origin = getRequestOriginWithForceHTTPS(req);
|
||||||
|
const backendCallbackUri = `${origin}/users/oidc/callback`;
|
||||||
|
|
||||||
authLogger.info("OIDC authorize request headers", {
|
authLogger.info("OIDC authorize request headers", {
|
||||||
protocol: req.protocol,
|
protocol: req.protocol,
|
||||||
host: req.get("Host"),
|
host: req.get("Host"),
|
||||||
@@ -807,6 +811,8 @@ router.get("/oidc/authorize", async (req, res) => {
|
|||||||
"x-forwarded-host": req.get("X-Forwarded-Host"),
|
"x-forwarded-host": req.get("X-Forwarded-Host"),
|
||||||
"x-forwarded-port": req.get("X-Forwarded-Port"),
|
"x-forwarded-port": req.get("X-Forwarded-Port"),
|
||||||
secure: req.secure,
|
secure: req.secure,
|
||||||
|
calculatedOrigin: origin,
|
||||||
|
backendCallbackUri: backendCallbackUri,
|
||||||
});
|
});
|
||||||
|
|
||||||
const envConfig = getOIDCConfigFromEnv();
|
const envConfig = getOIDCConfigFromEnv();
|
||||||
@@ -826,15 +832,6 @@ router.get("/oidc/authorize", async (req, res) => {
|
|||||||
const state = nanoid();
|
const state = nanoid();
|
||||||
const nonce = nanoid();
|
const nonce = nanoid();
|
||||||
|
|
||||||
const protocol =
|
|
||||||
process.env.OIDC_FORCE_HTTPS === "true"
|
|
||||||
? "https"
|
|
||||||
: req.get("X-Forwarded-Proto") || req.protocol;
|
|
||||||
|
|
||||||
const host = req.get("Host");
|
|
||||||
const origin = `${protocol}://${host}`;
|
|
||||||
const backendCallbackUri = `${origin}/users/oidc/callback`;
|
|
||||||
|
|
||||||
const referer = req.get("Referer");
|
const referer = req.get("Referer");
|
||||||
let frontendOrigin;
|
let frontendOrigin;
|
||||||
if (referer) {
|
if (referer) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { FieldCrypto } from "../utils/field-crypto.js";
|
|||||||
import { promises as fs } from "fs";
|
import { promises as fs } from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { getRequestOrigin } from "../utils/request-origin.js";
|
||||||
|
|
||||||
const AUTH_TIMEOUT = 60 * 1000;
|
const AUTH_TIMEOUT = 60 * 1000;
|
||||||
|
|
||||||
@@ -48,43 +49,6 @@ interface OPKSSHAuthSession {
|
|||||||
const activeAuthSessions = new Map<string, OPKSSHAuthSession>();
|
const activeAuthSessions = new Map<string, OPKSSHAuthSession>();
|
||||||
const cleanupInProgress = new Set<string>();
|
const cleanupInProgress = new Set<string>();
|
||||||
|
|
||||||
export function getRequestOrigin(req: IncomingMessage): string {
|
|
||||||
const protoHeader =
|
|
||||||
req.headers["x-forwarded-proto"] ||
|
|
||||||
((req.socket as unknown as { encrypted?: boolean }).encrypted
|
|
||||||
? "https"
|
|
||||||
: "http");
|
|
||||||
const proto =
|
|
||||||
typeof protoHeader === "string"
|
|
||||||
? protoHeader.split(",")[0].trim()
|
|
||||||
: String(protoHeader);
|
|
||||||
|
|
||||||
const portHeader = req.headers["x-forwarded-port"];
|
|
||||||
const port =
|
|
||||||
typeof portHeader === "string"
|
|
||||||
? portHeader.split(",")[0].trim()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const hostHeaderRaw =
|
|
||||||
req.headers["x-forwarded-host"] || req.headers.host || "localhost";
|
|
||||||
const hostHeader =
|
|
||||||
typeof hostHeaderRaw === "string"
|
|
||||||
? hostHeaderRaw.split(",")[0].trim()
|
|
||||||
: String(hostHeaderRaw);
|
|
||||||
|
|
||||||
if (port) {
|
|
||||||
const hostWithoutPort = hostHeader.split(":")[0];
|
|
||||||
const isDefaultPort =
|
|
||||||
(proto === "http" && port === "80") ||
|
|
||||||
(proto === "https" && port === "443");
|
|
||||||
return isDefaultPort
|
|
||||||
? `${proto}://${hostWithoutPort}`
|
|
||||||
: `${proto}://${hostWithoutPort}:${port}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${proto}://${hostHeader}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOPKConfigPath(): string {
|
function getOPKConfigPath(): string {
|
||||||
const dataDir =
|
const dataDir =
|
||||||
process.env.DATA_DIR || path.join(process.cwd(), "db", "data");
|
process.env.DATA_DIR || path.join(process.cwd(), "db", "data");
|
||||||
|
|||||||
@@ -1001,6 +1001,28 @@ class PollingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshAllPolling(): void {
|
||||||
|
const hostsToRefresh: Array<{
|
||||||
|
host: SSHHostWithCredentials;
|
||||||
|
viewerUserId?: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const [hostId, config] of this.pollingConfigs.entries()) {
|
||||||
|
hostsToRefresh.push({
|
||||||
|
host: config.host,
|
||||||
|
viewerUserId: config.viewerUserId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const hostId of this.pollingConfigs.keys()) {
|
||||||
|
this.stopPollingForHost(hostId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { host, viewerUserId } of hostsToRefresh) {
|
||||||
|
this.startPollingForHost(host, { statusOnly: true, viewerUserId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registerViewer(hostId: number, sessionId: string, userId: string): void {
|
registerViewer(hostId: number, sessionId: string, userId: string): void {
|
||||||
if (!this.activeViewers.has(hostId)) {
|
if (!this.activeViewers.has(hostId)) {
|
||||||
this.activeViewers.set(hostId, new Set());
|
this.activeViewers.set(hostId, new Set());
|
||||||
@@ -1259,9 +1281,8 @@ async function resolveHostCredentials(
|
|||||||
const isSharedHost = userId !== ownerId;
|
const isSharedHost = userId !== ownerId;
|
||||||
|
|
||||||
if (isSharedHost) {
|
if (isSharedHost) {
|
||||||
const { SharedCredentialManager } = await import(
|
const { SharedCredentialManager } =
|
||||||
"../utils/shared-credential-manager.js"
|
await import("../utils/shared-credential-manager.js");
|
||||||
);
|
|
||||||
const sharedCredManager = SharedCredentialManager.getInstance();
|
const sharedCredManager = SharedCredentialManager.getInstance();
|
||||||
const sharedCred = await sharedCredManager.getSharedCredentialForUser(
|
const sharedCred = await sharedCredManager.getSharedCredentialForUser(
|
||||||
host.id as number,
|
host.id as number,
|
||||||
@@ -3185,6 +3206,9 @@ app.post("/global-settings", requireAdmin, async (req, res) => {
|
|||||||
.run(String(metricsInterval));
|
.run(String(metricsInterval));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh all active polling to apply new intervals immediately
|
||||||
|
pollingManager.refreshAllPolling();
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
statsLogger.error("Failed to save global settings", {
|
statsLogger.error("Failed to save global settings", {
|
||||||
|
|||||||
@@ -808,9 +808,9 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
|||||||
case "opkssh_start_auth": {
|
case "opkssh_start_auth": {
|
||||||
const opksshData = data as { hostId: number };
|
const opksshData = data as { hostId: number };
|
||||||
try {
|
try {
|
||||||
const { startOPKSSHAuth, getRequestOrigin } = await import(
|
const { startOPKSSHAuth } = await import("./opkssh-auth.js");
|
||||||
"./opkssh-auth.js"
|
const { getRequestOrigin } =
|
||||||
);
|
await import("../utils/request-origin.js");
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const hostRow = await db
|
const hostRow = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import type { Request } from "express";
|
||||||
|
import type { IncomingMessage } from "http";
|
||||||
|
|
||||||
|
export function getRequestOrigin(req: Request | IncomingMessage): string {
|
||||||
|
let protocol: string;
|
||||||
|
const protoHeader = req.headers["x-forwarded-proto"];
|
||||||
|
|
||||||
|
if (protoHeader) {
|
||||||
|
protocol =
|
||||||
|
typeof protoHeader === "string"
|
||||||
|
? protoHeader.split(",")[0].trim()
|
||||||
|
: protoHeader[0];
|
||||||
|
} else if ("protocol" in req && req.protocol) {
|
||||||
|
protocol = req.protocol;
|
||||||
|
} else {
|
||||||
|
protocol = (req.socket as unknown as { encrypted?: boolean }).encrypted
|
||||||
|
? "https"
|
||||||
|
: "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
const portHeader = req.headers["x-forwarded-port"];
|
||||||
|
const port =
|
||||||
|
typeof portHeader === "string"
|
||||||
|
? portHeader.split(",")[0].trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const hostHeaderRaw =
|
||||||
|
req.headers["x-forwarded-host"] || req.headers.host || "localhost";
|
||||||
|
const hostHeader =
|
||||||
|
typeof hostHeaderRaw === "string"
|
||||||
|
? hostHeaderRaw.split(",")[0].trim()
|
||||||
|
: String(hostHeaderRaw);
|
||||||
|
|
||||||
|
if (port) {
|
||||||
|
const hostWithoutPort = hostHeader.split(":")[0];
|
||||||
|
const isDefaultPort =
|
||||||
|
(protocol === "http" && port === "80") ||
|
||||||
|
(protocol === "https" && port === "443");
|
||||||
|
|
||||||
|
return isDefaultPort
|
||||||
|
? `${protocol}://${hostWithoutPort}`
|
||||||
|
: `${protocol}://${hostWithoutPort}:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${protocol}://${hostHeader}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRequestOriginWithForceHTTPS(
|
||||||
|
req: Request | IncomingMessage,
|
||||||
|
): string {
|
||||||
|
if (process.env.OIDC_FORCE_HTTPS === "true") {
|
||||||
|
const origin = getRequestOrigin(req);
|
||||||
|
return origin.replace(/^http:/, "https:");
|
||||||
|
}
|
||||||
|
return getRequestOrigin(req);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user