From 7b2156446ce0e9a8bb47fed26e446f5491da7123 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sun, 14 Jun 2026 16:06:26 +0800 Subject: [PATCH] fix(mongodb): use tls param for form connections --- .../connection/ConnectionDialog.vue | 4 +- .../desktop/src/lib/mongoConnectionOptions.ts | 8 ++++ crates/dbx-core/src/models/connection.rs | 44 ++++++++++++++----- .../app-tests/mongoConnectionOptions.test.ts | 12 +++++ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 463a347bc..a18720150 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -2510,13 +2510,13 @@ function openExternalUrl(url: string) {
- +
diff --git a/apps/desktop/src/lib/mongoConnectionOptions.ts b/apps/desktop/src/lib/mongoConnectionOptions.ts index df1825860..e16634170 100644 --- a/apps/desktop/src/lib/mongoConnectionOptions.ts +++ b/apps/desktop/src/lib/mongoConnectionOptions.ts @@ -15,6 +15,14 @@ export function setMongoUrlParam(urlParams: string | undefined, key: string, val } export function mongodbAuthFailureHint(message: string): string { + if (message.includes("no records found") && message.includes("_mongodb._tcp.")) { + return `${message}\n\nMongoDB SRV URLs require a DNS hostname with SRV records. For an IP address or normal host:port endpoint, use mongodb://host:port instead of mongodb+srv://host.`; + } + + if (message.includes("ReplicaSetNoPrimary") && message.includes("127.0.0.1:27017")) { + return `${message}\n\nThe replica set is advertising 127.0.0.1:27017, which points back to this app machine instead of the MongoDB server. Add directConnection=true for a single reachable endpoint, or reconfigure the replica set members to advertise addresses reachable from this app.`; + } + if (message.includes("must be URL encoded") || message.includes("cannot contain unescaped %")) { return `${message}\n\nMongoDB URL mode requires reserved characters in usernames and passwords to be percent-encoded. For example, @ becomes %40, # becomes %23, / becomes %2F, : becomes %3A, and % becomes %25.`; } diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs index 706339749..7a3037c8c 100644 --- a/crates/dbx-core/src/models/connection.rs +++ b/crates/dbx-core/src/models/connection.rs @@ -695,10 +695,7 @@ impl ConnectionConfig { suffix.push_str("&directConnection=true"); } } - let scheme = if self.ssl { "mongodb+srv" } else { "mongodb" }; - // SRV URLs resolve the port via DNS SRV records, so we omit the explicit port. - let addr = if self.ssl { host.to_string() } else { format!("{host}:{port}") }; - format!("{scheme}://{addr}{db_part}{suffix}") + format!("mongodb://{host}:{port}{db_part}{suffix}") } DatabaseType::Oracle => format!("oracle://{host}:{port}{db_part}"), DatabaseType::Elasticsearch => { @@ -823,13 +820,10 @@ impl ConnectionConfig { suffix.push_str("&directConnection=true"); } } - let scheme = if self.ssl { "mongodb+srv" } else { "mongodb" }; - // SRV URLs resolve the port via DNS SRV records, so we omit the explicit port. - let addr = if self.ssl { host.to_string() } else { format!("{host}:{port}") }; if self.username.is_empty() { - format!("{scheme}://{addr}{db_part}{suffix}") + format!("mongodb://{host}:{port}{db_part}{suffix}") } else { - format!("{scheme}://{username}:{password}@{addr}{db_part}{suffix}") + format!("mongodb://{username}:{password}@{host}:{port}{db_part}{suffix}") } } DatabaseType::Oracle => { @@ -975,7 +969,7 @@ impl ConnectionConfig { normalize_bare_mysql_url_params(value) } DatabaseType::Postgres | DatabaseType::Redshift => normalize_postgres_url_params(value, self.ssl), - DatabaseType::MongoDb => value.trim_start_matches('?').to_string(), + DatabaseType::MongoDb => normalize_mongo_url_params(value, self.ssl), _ => value.trim_start_matches('?').to_string(), } } @@ -1065,6 +1059,18 @@ fn normalize_mysql_url_params(value: &str, force_tls: bool, accept_invalid_certs parts.join("&") } +fn normalize_mongo_url_params(value: &str, force_tls: bool) -> String { + let value = value.trim_start_matches('?'); + let mut parts: Vec = value.split('&').filter(|part| !part.is_empty()).map(str::to_string).collect(); + + if force_tls { + parts.retain(|part| !url_param_key_is(part, "tls") && !url_param_key_is(part, "ssl")); + parts.insert(0, "tls=true".to_string()); + } + + parts.join("&") +} + fn normalize_postgres_url_params(value: &str, force_tls: bool) -> String { let value = value.trim_start_matches('?'); @@ -2011,6 +2017,24 @@ mod tests { assert!(!url.contains("secret")); } + #[test] + fn mongodb_form_tls_uses_standard_scheme_and_tls_param() { + let mut config = mongodb_config("root", "secret", Some("admin")); + config.ssl = true; + + assert_eq!(config.connection_url(), "mongodb://root:secret@10.1.2.3:17000/admin?tls=true"); + assert_eq!(config.redacted_connection_url(), "mongodb://10.1.2.3:17000/admin?tls=true"); + } + + #[test] + fn mongodb_form_tls_replaces_existing_tls_params() { + let mut config = mongodb_config("root", "secret", Some("admin")); + config.ssl = true; + config.url_params = Some("authSource=admin&ssl=false&tls=false".to_string()); + + assert_eq!(config.connection_url(), "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin"); + } + #[test] fn parse_mongo_first_host_replica_set() { let uri = "mongodb://user:pass@host1:27017,host2:27017,host3:27017/admin?replicaSet=rs0"; diff --git a/packages/app-tests/mongoConnectionOptions.test.ts b/packages/app-tests/mongoConnectionOptions.test.ts index c2994c446..6190329aa 100644 --- a/packages/app-tests/mongoConnectionOptions.test.ts +++ b/packages/app-tests/mongoConnectionOptions.test.ts @@ -29,6 +29,18 @@ test("adds a MongoDB URL encoding hint for reserved password characters", () => assert.match(mongodbAuthFailureHint(message), /@ becomes %40/); }); +test("adds a MongoDB SRV DNS hint for IP endpoints", () => { + const message = 'MongoDB connection failed: Kind: An error occurred during DNS resolution: DNS error: no records found for Query { name: Name("_mongodb._tcp.172.17.0.1."), query_type: SRV }'; + + assert.match(mongodbAuthFailureHint(message), /use mongodb:\/\/host:port instead of mongodb\+srv:\/\/host/); +}); + +test("adds a MongoDB replica set localhost advertisement hint", () => { + const message = "MongoDB connection failed: Kind: Server selection timeout: No available servers. Topology: { Type: ReplicaSetNoPrimary, Set Name: LIMLIU, Servers: [ { Address: 127.0.0.1:27017, Type: Unknown, Error: Kind: I/O error: Connection refused (os error 111) } ] }"; + + assert.match(mongodbAuthFailureHint(message), /Add directConnection=true/); +}); + test("adds a MongoDB listDatabases permission hint", () => { const message = "Command failed with error 13 (Unauthorized): not authorized on admin to execute command { listDatabases: 1 }";