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 }";