fix(mongodb): remove invalid direct connection
This commit is contained in:
parent
2c71b9dc06
commit
a1e39333a3
|
|
@ -15,6 +15,10 @@ export function setMongoUrlParam(urlParams: string | undefined, key: string, val
|
|||
}
|
||||
|
||||
export function mongodbAuthFailureHint(message: string): string {
|
||||
if (message.includes("cannot specify multiple seeds with directConnection=true")) {
|
||||
return `${message}\n\nMongoDB directConnection=true can only be used with a single host. Remove directConnection=true when using a multi-host replica set URL, or keep only one reachable host if you need a direct connection.`;
|
||||
}
|
||||
|
||||
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.`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -713,10 +713,11 @@ impl ConnectionConfig {
|
|||
DatabaseType::MongoDb => {
|
||||
let is_tunneled = host != self.host.as_str() || port != self.port;
|
||||
if let Some(cs) = self.connection_string.as_deref().filter(|s| !s.is_empty()) {
|
||||
let cs = normalize_mongo_uri_direct_connection(cs);
|
||||
if is_tunneled {
|
||||
return rewrite_mongo_uri_host(cs, &host, port);
|
||||
return rewrite_mongo_uri_host(&cs, &host, port);
|
||||
}
|
||||
return cs.to_string();
|
||||
return cs;
|
||||
}
|
||||
let mut suffix = if params.is_empty() { String::new() } else { format!("?{params}") };
|
||||
if is_tunneled && !suffix.contains("directConnection=") {
|
||||
|
|
@ -840,10 +841,11 @@ impl ConnectionConfig {
|
|||
DatabaseType::MongoDb => {
|
||||
let is_tunneled = host != self.host.as_str() || port != self.port;
|
||||
if let Some(cs) = self.connection_string.as_deref().filter(|s| !s.is_empty()) {
|
||||
let cs = normalize_mongo_uri_direct_connection(cs);
|
||||
if is_tunneled {
|
||||
return rewrite_mongo_uri_host(cs, &host, port);
|
||||
return rewrite_mongo_uri_host(&cs, &host, port);
|
||||
}
|
||||
return cs.to_string();
|
||||
return cs;
|
||||
}
|
||||
let mut suffix = if params.is_empty() { String::new() } else { format!("?{params}") };
|
||||
if is_tunneled && !suffix.contains("directConnection=") {
|
||||
|
|
@ -1125,6 +1127,58 @@ fn normalize_mongo_url_params(value: &str, force_tls: bool) -> String {
|
|||
parts.join("&")
|
||||
}
|
||||
|
||||
fn normalize_mongo_uri_direct_connection(uri: &str) -> String {
|
||||
if !mongo_uri_has_multiple_seeds(uri) || !mongo_uri_has_direct_connection_true(uri) {
|
||||
return uri.to_string();
|
||||
}
|
||||
|
||||
let (before_fragment, fragment) =
|
||||
uri.split_once('#').map(|(base, fragment)| (base, Some(fragment))).unwrap_or((uri, None));
|
||||
let Some((base, query)) = before_fragment.split_once('?') else {
|
||||
return uri.to_string();
|
||||
};
|
||||
let params =
|
||||
query.split('&').filter(|part| !mongo_url_param_is_direct_connection_true(part)).collect::<Vec<_>>().join("&");
|
||||
|
||||
let mut normalized = if params.is_empty() { base.to_string() } else { format!("{base}?{params}") };
|
||||
if let Some(fragment) = fragment {
|
||||
normalized.push('#');
|
||||
normalized.push_str(fragment);
|
||||
}
|
||||
normalized
|
||||
}
|
||||
|
||||
fn mongo_uri_has_multiple_seeds(uri: &str) -> bool {
|
||||
mongo_uri_host_section(uri)
|
||||
.map(|hosts| hosts.split(',').filter(|host| !host.trim().is_empty()).count() > 1)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn mongo_uri_host_section(uri: &str) -> Option<&str> {
|
||||
let rest = uri.strip_prefix("mongodb://").or_else(|| uri.strip_prefix("mongodb+srv://"))?;
|
||||
let authority = rest.split('/').next()?.split('?').next().unwrap_or(rest);
|
||||
Some(match authority.rfind('@') {
|
||||
Some(idx) => &authority[idx + 1..],
|
||||
None => authority,
|
||||
})
|
||||
}
|
||||
|
||||
fn mongo_uri_has_direct_connection_true(uri: &str) -> bool {
|
||||
uri.split_once('?')
|
||||
.map(|(_, query)| {
|
||||
query.split('#').next().unwrap_or("").split('&').any(mongo_url_param_is_direct_connection_true)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn mongo_url_param_is_direct_connection_true(part: &str) -> bool {
|
||||
let Some((key, value)) = part.split_once('=') else {
|
||||
return false;
|
||||
};
|
||||
percent_decode_str(key).decode_utf8_lossy().eq_ignore_ascii_case("directConnection")
|
||||
&& percent_decode_str(value).decode_utf8_lossy().eq_ignore_ascii_case("true")
|
||||
}
|
||||
|
||||
fn normalize_postgres_url_params(value: &str, force_tls: bool) -> String {
|
||||
let value = value.trim_start_matches('?');
|
||||
|
||||
|
|
@ -2147,6 +2201,30 @@ mod tests {
|
|||
assert_eq!(url, "mongodb://read:pass@host1:27017,host2:27017/admin?replicaSet=rs0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mongodb_multi_seed_connection_string_removes_direct_connection_true() {
|
||||
let mut config = mongodb_config("root", "secret", Some("admin"));
|
||||
config.connection_string = Some(
|
||||
"mongodb://read:pass@host1:27017,host2:27017/admin?directConnection=true&replicaSet=rs0&authSource=admin"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let url = config.connection_url();
|
||||
|
||||
assert_eq!(url, "mongodb://read:pass@host1:27017,host2:27017/admin?replicaSet=rs0&authSource=admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mongodb_single_seed_connection_string_keeps_direct_connection_true() {
|
||||
let mut config = mongodb_config("root", "secret", Some("admin"));
|
||||
config.connection_string =
|
||||
Some("mongodb://read:pass@host1:27017/admin?directConnection=true&authSource=admin".to_string());
|
||||
|
||||
let url = config.connection_url();
|
||||
|
||||
assert_eq!(url, "mongodb://read:pass@host1:27017/admin?directConnection=true&authSource=admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mongodb_form_url_adds_direct_connection_when_tunneled() {
|
||||
let mut config = mongodb_config("root", "secret", Some("admin"));
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ test("adds a MongoDB URL encoding hint for reserved password characters", () =>
|
|||
assert.match(mongodbAuthFailureHint(message), /@ becomes %40/);
|
||||
});
|
||||
|
||||
test("adds a MongoDB directConnection multi-host hint", () => {
|
||||
const message = "MongoDB connection failed: Kind: An invalid argument was provided: cannot specify multiple seeds with directConnection=true";
|
||||
|
||||
assert.match(mongodbAuthFailureHint(message), /Remove directConnection=true when using a multi-host replica set URL/);
|
||||
});
|
||||
|
||||
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 }';
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue