From 849caf4b394062538c93639a16ca260c604752bf Mon Sep 17 00:00:00 2001 From: jischeng <49861575+jischeng@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:05:57 +0800 Subject: [PATCH] feat(ssh): probe none authentication before credentials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some SSH proxies and jump-hosts authenticate with the "none" method, requiring no password or key. dbx only tried publickey/password/agent, so connecting through such a host failed with "SSH public key authentication failed" — and the frontend forced users to enter a credential just to pass validation. - Backend: probe authenticate_none first in connect_and_authenticate; on rejection fall back to the configured credential method, matching standard openssh behavior. - Frontend: drop the "password/key/agent required" check in validateTransportLayers so passwordless hops can be saved and tested. Refs #1628 --- .../components/connection/ConnectionDialog.vue | 6 +++--- crates/dbx-core/src/db/ssh_tunnel.rs | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index e1e193c113..240d374c3b 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -2139,9 +2139,9 @@ function validateTransportLayers(config: LegacyConnectionConfig) { } if (layer.type === "ssh") { if (!layer.user?.trim()) throw new Error(t("connection.sshHopInvalidUser", { hop: label })); - if (!layer.password?.trim() && !layer.key_path?.trim() && !layer.use_ssh_agent) { - throw new Error(t("connection.sshHopInvalidAuth", { hop: label })); - } + // Auth credentials are optional: the backend probes "none" authentication + // first, so hops that require no credential (e.g. passwordless SSH proxies) + // are valid with password, key, and agent all left empty. const timeout = Number(layer.connect_timeout_secs); if (!Number.isFinite(timeout) || timeout < 1 || timeout > 300) { throw new Error(t("connection.sshHopInvalidTimeout", { hop: label })); diff --git a/crates/dbx-core/src/db/ssh_tunnel.rs b/crates/dbx-core/src/db/ssh_tunnel.rs index 608921eab2..2666e928ff 100644 --- a/crates/dbx-core/src/db/ssh_tunnel.rs +++ b/crates/dbx-core/src/db/ssh_tunnel.rs @@ -77,6 +77,18 @@ async fn connect_and_authenticate( .map_err(|_| format!("SSH connection timed out ({connect_timeout_secs}s)"))? .map_err(|e| format!("SSH connection failed: {e}"))?; + // Probe with "none" authentication first. Some SSH proxies and jump-hosts + // accept connections without any credential, and this is also the standard + // SSH probe used to discover the auth methods the server supports. + let none_res = tokio::time::timeout(connect_timeout, session.authenticate_none(ssh_user)) + .await + .map_err(|_| format!("SSH auth probe timed out ({connect_timeout_secs}s)"))? + .map_err(|e| format!("SSH auth probe failed: {e}"))?; + if none_res.success() { + return Ok(session); + } + + // "none" was rejected — fall back to the configured credential method. if !ssh_key_path.is_empty() { // Validate SSH key file path validate_file_path(ssh_key_path, |_| false)?; @@ -114,7 +126,10 @@ async fn connect_and_authenticate( Err(agent_err) => return Err(agent_err), } } else { - return Err("No SSH authentication method provided (password, key, or ssh-agent)".to_string()); + return Err( + "SSH authentication failed: \"none\" was rejected and no password, key, or ssh-agent is configured" + .to_string(), + ); } Ok(session)