fix(ssh): restore agent authentication for new tunnels

Closes #3796
This commit is contained in:
t8y2 committed 2026-08-12 23:13:51 +00:00
1 parent 3acf3a66b9
commit 16e3bb7d7a
5 files changed
+104 -53

No files matched your search

@@ -0,0 +1,13 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
describe("SSH agent connection form", () => {
it("offers SSH agent for a new tunnel and exposes its custom socket path", () => {
expect(dialogSource).toContain('<SelectItem value="agent">{{ t("connection.sshUseAgent") }}</SelectItem>');
expect(dialogSource).toContain('<div v-if="selectedSshLayer.auth_method === \'agent\'" class="grid grid-cols-4 items-center gap-4">');
expect(dialogSource).toContain('v-model="selectedSshLayer.ssh_agent_sock_path"');
expect(dialogSource).not.toContain('v-if="isLegacySshAgentMethod(selectedSshLayer)" value="agent" disabled');
});
});
@@ -0,0 +1,61 @@
import { describe, expect, it } from "vitest";
import type { SshTunnelConfig } from "@/types/database";
import { applySshAuthMethod, inferSshAuthMethod } from "../sshAuthMethod";
function sshLayer(overrides: Partial<SshTunnelConfig> = {}): SshTunnelConfig {
return {
id: "ssh-1",
host: "jump.example.com",
port: 22,
user: "root",
password: "password-secret",
key_path: "~/.ssh/id_ed25519",
key_passphrase: "key-secret",
use_ssh_agent: false,
ssh_agent_sock_path: "/custom/agent.sock",
auth_method: "password",
...overrides,
};
}
describe("SSH authentication method state", () => {
it("enables agent authentication while preserving its custom socket", () => {
const layer = sshLayer();
applySshAuthMethod(layer, "agent");
expect(layer).toMatchObject({
auth_method: "agent",
use_ssh_agent: true,
ssh_agent_sock_path: "/custom/agent.sock",
password: "",
key_path: "",
key_passphrase: "",
});
});
it.each([
["password", "password-secret", "", ""],
["key", "", "~/.ssh/id_ed25519", "key-secret"],
["key+password", "password-secret", "~/.ssh/id_ed25519", "key-secret"],
["none", "", "", ""],
] as const)("disables agent when switching to %s", (method, password, keyPath, keyPassphrase) => {
const layer = sshLayer({ auth_method: "agent", use_ssh_agent: true });
applySshAuthMethod(layer, method);
expect(layer.auth_method).toBe(method);
expect(layer.use_ssh_agent).toBe(false);
expect(layer.password).toBe(password);
expect(layer.key_path).toBe(keyPath);
expect(layer.key_passphrase).toBe(keyPassphrase);
expect(layer.ssh_agent_sock_path).toBe("/custom/agent.sock");
});
it("infers agent for a legacy connection without an auth method", () => {
const layer = sshLayer({ auth_method: undefined, password: "", key_path: "", use_ssh_agent: true });
expect(inferSshAuthMethod(layer)).toBe("agent");
expect(layer.ssh_agent_sock_path).toBe("/custom/agent.sock");
});
});
@@ -0,0 +1,22 @@
import type { SshTunnelConfig } from "@/types/database";
export type SshAuthMethod = NonNullable<SshTunnelConfig["auth_method"]>;
export function inferSshAuthMethod(hop: Partial<SshTunnelConfig>): SshAuthMethod {
if (hop.key_path?.trim()) return "key";
if (hop.password) return "password";
if (hop.use_ssh_agent) return "agent";
return "none";
}
export function applySshAuthMethod(layer: SshTunnelConfig, value: unknown) {
const method: SshAuthMethod = value === "key" ? "key" : value === "key+password" ? "key+password" : value === "agent" ? "agent" : value === "none" ? "none" : "password";
layer.auth_method = method;
layer.use_ssh_agent = method === "agent";
if (method !== "password" && method !== "key+password") layer.password = "";
if (method !== "key" && method !== "key+password") {
layer.key_path = "";
layer.key_passphrase = "";
}
}