fix(mongodb): remove invalid direct connection
This commit is contained in:
1 parent
2c71b9dc06
commit
a1e39333a3
3 files changed
+92
-4
No files matched your search
@@ -15,6 +15,10 @@ export function setMongoUrlParam(urlParams: string | undefined, key: string, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function mongodbAuthFailureHint(message: string): string {
|
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.")) {
|
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.`;
|
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 => {
|
DatabaseType::MongoDb => {
|
||||||
let is_tunneled = host != self.host.as_str() || port != self.port;
|
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()) {
|
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 {
|
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}") };
|
let mut suffix = if params.is_empty() { String::new() } else { format!("?{params}") };
|
||||||
if is_tunneled && !suffix.contains("directConnection=") {
|
if is_tunneled && !suffix.contains("directConnection=") {
|
||||||
@@ -840,10 +841,11 @@ impl ConnectionConfig {
|
|||||||
DatabaseType::MongoDb => {
|
DatabaseType::MongoDb => {
|
||||||
let is_tunneled = host != self.host.as_str() || port != self.port;
|
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()) {
|
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 {
|
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}") };
|
let mut suffix = if params.is_empty() { String::new() } else { format!("?{params}") };
|
||||||
if is_tunneled && !suffix.contains("directConnection=") {
|
if is_tunneled && !suffix.contains("directConnection=") {
|
||||||
@@ -1125,6 +1127,58 @@ fn normalize_mongo_url_params(value: &str, force_tls: bool) -> String {
|
|||||||
parts.join("&")
|
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 {
|
fn normalize_postgres_url_params(value: &str, force_tls: bool) -> String {
|
||||||
let value = value.trim_start_matches('?');
|
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");
|
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]
|
#[test]
|
||||||
fn mongodb_form_url_adds_direct_connection_when_tunneled() {
|
fn mongodb_form_url_adds_direct_connection_when_tunneled() {
|
||||||
let mut config = mongodb_config("root", "secret", Some("admin"));
|
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/);
|
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", () => {
|
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 }';
|
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 }';
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user