fix(mongodb): use tls param for form connections

This commit is contained in:
t8y2 2026-06-14 16:06:26 +08:00
parent f385386284
commit 7b2156446c
4 changed files with 56 additions and 12 deletions

View File

@ -2510,13 +2510,13 @@ function openExternalUrl(url: string) {
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.host") }}</Label>
<Input v-model="form.host" class="col-span-2" />
<Input v-model.number="form.port" type="number" class="col-span-1" :disabled="form.ssl" />
<Input v-model.number="form.port" type="number" class="col-span-1" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<span />
<label class="col-span-3 flex items-center gap-2 text-sm">
<input type="checkbox" v-model="form.ssl" class="mr-0" />
<span>SRV (MongoDB Atlas)</span>
<span>{{ t("connection.sslEnable") }}</span>
</label>
</div>
<div class="grid grid-cols-4 items-center gap-4">

View File

@ -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.`;
}

View File

@ -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<String> = 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";

View File

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