mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-27 16:37:26 +00:00
Fix synced client tunnel endpoints (#1302)
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
} from "./ssh-primitives.js";
|
||||
import { sendC2SMessage, writeC2SRemoteChunk } from "./c2s-relay-utils.js";
|
||||
import { getTunnelMode } from "./utils.js";
|
||||
import { createCurrentHostResolutionRepository } from "../../database/repositories/factory.js";
|
||||
|
||||
export type C2SOpenMessage = {
|
||||
type: "open" | "test";
|
||||
@@ -25,17 +26,36 @@ export type C2SOpenMessage = {
|
||||
const permissionManager = PermissionManager.getInstance();
|
||||
let c2sRemoteStreamCounter = 0;
|
||||
|
||||
export async function resolveC2SSourceHostId(
|
||||
tunnelConfig: Partial<TunnelConfig>,
|
||||
findHostIdBySyncId: (syncId: string) => Promise<number | null>,
|
||||
): Promise<number> {
|
||||
const sourceHostSyncId = tunnelConfig.sourceHostSyncId?.trim();
|
||||
if (sourceHostSyncId) {
|
||||
const remoteHostId = await findHostIdBySyncId(sourceHostSyncId);
|
||||
if (!remoteHostId) {
|
||||
throw new Error("Endpoint SSH host was not found on the remote server");
|
||||
}
|
||||
return remoteHostId;
|
||||
}
|
||||
|
||||
if (!tunnelConfig.sourceHostId) {
|
||||
throw new Error("Endpoint SSH host is required");
|
||||
}
|
||||
return tunnelConfig.sourceHostId;
|
||||
}
|
||||
|
||||
async function resolveC2STunnelSource(
|
||||
tunnelConfig: Partial<TunnelConfig>,
|
||||
userId: string,
|
||||
): Promise<TunnelConfig> {
|
||||
if (!tunnelConfig.sourceHostId) {
|
||||
throw new Error("Endpoint SSH host is required");
|
||||
}
|
||||
const sourceHostId = await resolveC2SSourceHostId(tunnelConfig, (syncId) =>
|
||||
createCurrentHostResolutionRepository().findHostIdBySyncId(syncId),
|
||||
);
|
||||
|
||||
const accessInfo = await permissionManager.canAccessHost(
|
||||
userId,
|
||||
tunnelConfig.sourceHostId,
|
||||
sourceHostId,
|
||||
"connect",
|
||||
);
|
||||
if (!accessInfo.hasAccess) {
|
||||
@@ -43,13 +63,13 @@ async function resolveC2STunnelSource(
|
||||
}
|
||||
|
||||
const { resolveHostById } = await import("../host-resolver.js");
|
||||
const resolvedHost = await resolveHostById(tunnelConfig.sourceHostId, userId);
|
||||
const resolvedHost = await resolveHostById(sourceHostId, userId);
|
||||
if (!resolvedHost) {
|
||||
throw new Error("Endpoint SSH host not found");
|
||||
}
|
||||
|
||||
return {
|
||||
name: tunnelConfig.name || `c2s:${tunnelConfig.sourceHostId}`,
|
||||
name: tunnelConfig.name || `c2s:${sourceHostId}`,
|
||||
scope: "c2s",
|
||||
mode: tunnelConfig.mode || "local",
|
||||
tunnelType:
|
||||
@@ -57,7 +77,7 @@ async function resolveC2STunnelSource(
|
||||
(tunnelConfig.mode === "remote" ? "remote" : "local"),
|
||||
bindHost: tunnelConfig.bindHost,
|
||||
targetHost: tunnelConfig.targetHost || "127.0.0.1",
|
||||
sourceHostId: resolvedHost.id || tunnelConfig.sourceHostId,
|
||||
sourceHostId: resolvedHost.id || sourceHostId,
|
||||
tunnelIndex: tunnelConfig.tunnelIndex || 0,
|
||||
requestingUserId: userId,
|
||||
hostName:
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveC2SSourceHostId } from "../../../hosts/tunnel/c2s-relay.js";
|
||||
|
||||
describe("resolveC2SSourceHostId", () => {
|
||||
it("uses the remote row matching the stable sync id", async () => {
|
||||
const findBySyncId = vi.fn().mockResolvedValue(42);
|
||||
|
||||
await expect(
|
||||
resolveC2SSourceHostId(
|
||||
{ sourceHostId: 7, sourceHostSyncId: "host-sync-id" },
|
||||
findBySyncId,
|
||||
),
|
||||
).resolves.toBe(42);
|
||||
expect(findBySyncId).toHaveBeenCalledWith("host-sync-id");
|
||||
});
|
||||
|
||||
it("keeps legacy local ids when no sync id is available", async () => {
|
||||
await expect(
|
||||
resolveC2SSourceHostId({ sourceHostId: 7 }, vi.fn()),
|
||||
).resolves.toBe(7);
|
||||
});
|
||||
|
||||
it("does not silently fall back to a mismatched id", async () => {
|
||||
await expect(
|
||||
resolveC2SSourceHostId(
|
||||
{ sourceHostId: 7, sourceHostSyncId: "missing" },
|
||||
vi.fn().mockResolvedValue(null),
|
||||
),
|
||||
).rejects.toThrow("not found on the remote server");
|
||||
});
|
||||
});
|
||||
@@ -482,6 +482,7 @@ export interface TunnelConnection {
|
||||
tunnelType?: "local" | "remote";
|
||||
bindHost?: string;
|
||||
sourceHostId?: number;
|
||||
sourceHostSyncId?: string;
|
||||
sourceHostName?: string;
|
||||
sourcePort: number;
|
||||
endpointPort: number;
|
||||
@@ -508,6 +509,7 @@ export interface TunnelConfig {
|
||||
targetHost?: string;
|
||||
|
||||
sourceHostId: number;
|
||||
sourceHostSyncId?: string;
|
||||
tunnelIndex: number;
|
||||
|
||||
requestingUserId?: string;
|
||||
|
||||
@@ -347,10 +347,20 @@ export function C2STunnelPresetManager(): React.ReactElement {
|
||||
getSSHHosts(),
|
||||
]);
|
||||
setHosts(nextHosts);
|
||||
const hostsById = new Map(nextHosts.map((host) => [host.id, host]));
|
||||
const normalizedConfig = Array.isArray(config)
|
||||
? (config as TunnelConnection[])
|
||||
.filter((tunnel) => tunnel.scope === "c2s")
|
||||
.map(normalizeClientTunnel)
|
||||
.map((tunnel) => {
|
||||
const sourceHost = tunnel.sourceHostId
|
||||
? hostsById.get(tunnel.sourceHostId)
|
||||
: undefined;
|
||||
return normalizeClientTunnel({
|
||||
...tunnel,
|
||||
sourceHostSyncId:
|
||||
tunnel.sourceHostSyncId || sourceHost?.syncId || undefined,
|
||||
});
|
||||
})
|
||||
: [];
|
||||
setLocalConfig(normalizedConfig);
|
||||
setSavedLocalConfig(normalizedConfig);
|
||||
@@ -473,6 +483,7 @@ export function C2STunnelPresetManager(): React.ReactElement {
|
||||
if (!host) return;
|
||||
updateTunnel(index, {
|
||||
sourceHostId: host.id,
|
||||
sourceHostSyncId: host.syncId || undefined,
|
||||
sourceHostName: host.name,
|
||||
endpointHost: host.name,
|
||||
endpointPort: 22,
|
||||
|
||||
Reference in New Issue
Block a user