diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue
index 6696542484..60d7edeb69 100644
--- a/apps/desktop/src/components/connection/ConnectionDialog.vue
+++ b/apps/desktop/src/components/connection/ConnectionDialog.vue
@@ -30,7 +30,7 @@ import { h2ConnectionModeForConfig, h2FileJdbcUrlWithPath, h2FilePathFromJdbcUrl
import { firstZooKeeperEndpoint, normalizeZooKeeperConnectString } from "@/lib/zookeeperConnection";
import { isLocalFileTypeDb } from "@/lib/connectionFile";
import { MQ_PINNED_VERSION_OPTIONS, pinnedVersionToSelection, selectionToPinnedVersion } from "@/lib/mqPinnedVersionOptions";
-import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions";
+import { mongodbAuthFailureHint, mongoUrlParam, mongoUrlParamIsTrue, normalizeMongoTlsFormState, setMongoUrlParam, setMongoUrlParamBoolean } from "@/lib/mongoConnectionOptions";
import { mysqlCleartextPasswordAuthEnabled, setMysqlCleartextPasswordAuthEnabled } from "@/lib/mysqlConnectionOptions";
import { copyToClipboard } from "@/lib/clipboard";
import { agentDriverInstallKey, appendAgentDriverUpdateHint, hasAgentDriverUpdate, showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint";
@@ -1786,6 +1786,20 @@ const mongoAuthMechanism = computed({
form.value.url_params = setMongoUrlParam(form.value.url_params, "authMechanism", value === "default" ? "" : value);
},
});
+const mongoTlsAllowInvalidCertificates = computed({
+ get: () => mongoUrlParamIsTrue(form.value.url_params, "tlsAllowInvalidCertificates"),
+ set: (value: boolean) => {
+ let next = setMongoUrlParamBoolean(form.value.url_params, "tlsAllowInvalidCertificates", value);
+ next = setMongoUrlParam(next, "tlsAllowInvalidHostnames", "");
+ form.value.url_params = next;
+ },
+});
+const mongoRetryWrites = computed({
+ get: () => mongoUrlParamIsTrue(form.value.url_params, "retryWrites", true),
+ set: (value: boolean) => {
+ form.value.url_params = setMongoUrlParamBoolean(form.value.url_params, "retryWrites", value, true);
+ },
+});
const mongoDriverMode = computed({
get: () => (form.value.driver_profile === "mongodb-legacy" ? "legacy" : "auto"),
set: (value: string) => {
@@ -2058,6 +2072,11 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
config.driver_profile = "mongodb";
config.driver_label = "MongoDB";
}
+ if (config.db_type === "mongodb") {
+ const mongoTls = normalizeMongoTlsFormState(!!config.ssl, config.url_params, config.ca_cert_path);
+ config.url_params = mongoTls.urlParams;
+ config.ca_cert_path = mongoTls.caCertPath;
+ }
if (config.db_type !== "oracle") {
config.sysdba = undefined;
config.oracle_connection_type = undefined;
@@ -2140,7 +2159,7 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
config.client_cert_path = undefined;
config.client_key_path = undefined;
}
- if (config.db_type !== "mysql" && config.db_type !== "clickhouse" && config.db_type !== "etcd" && config.db_type !== "starrocks") {
+ if (config.db_type !== "mysql" && config.db_type !== "clickhouse" && config.db_type !== "etcd" && config.db_type !== "starrocks" && config.db_type !== "mongodb") {
config.ca_cert_path = undefined;
} else {
config.ca_cert_path = config.ca_cert_path?.trim() || "";
@@ -3984,6 +4003,40 @@ function openExternalUrl(url: string) {
{{ t("connection.sslEnable") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("connection.caCertPathBrowse") }}
+
+
+
+
@@ -4015,7 +4068,7 @@ function openExternalUrl(url: string) {
-
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index d2a59cf24d..0480cd6299 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -385,6 +385,10 @@ export default {
dmCompatHint: "Requires DM8 ODBC driver installed on your system.",
dmDownload: "Download from Dameng",
mongoLegacyHint: "Use Legacy for older MongoDB servers, especially versions below 4.2. If authentication fails and the user was created in admin, set Auth DB to admin.",
+ mongoTlsAllowInvalidCertificates: "Disable TLS certificate verification",
+ mongoTlsAllowInvalidCertificatesHint: "Maps to tlsAllowInvalidCertificates=true and disables all certificate validation, not just hostname checks. This increases MITM risk. Commonly required for AWS DocumentDB over an SSH tunnel.",
+ mongoRetryWrites: "Retry writes",
+ mongoRetryWritesHint: "Maps to retryWrites. Disable for AWS DocumentDB, which does not support retryable writes.",
compatible: "Compatible",
mainstream: "Popular",
color: "Color",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index 9116611bb0..4e732ac18c 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -388,6 +388,10 @@ export default withEnglishFallback({
dmCompatHint: "Requiere el driver ODBC DM8 instalado en el sistema.",
dmDownload: "Descargar desde Dameng",
mongoLegacyHint: "Usa Legacy para servidores MongoDB antiguos, especialmente versiones anteriores a 4.2. Si falla la autenticación y el usuario se creó en admin, define Auth DB como admin.",
+ mongoTlsAllowInvalidCertificates: "Desactivar verificación de certificado TLS",
+ mongoTlsAllowInvalidCertificatesHint: "Se asigna a tlsAllowInvalidCertificates=true y desactiva toda la validación de certificados, no solo la del nombre de host. Aumenta el riesgo de MITM. Suele ser necesario para AWS DocumentDB a través de un túnel SSH.",
+ mongoRetryWrites: "Reintentar escrituras",
+ mongoRetryWritesHint: "Se asigna a retryWrites. Desactívalo para AWS DocumentDB, que no admite escrituras reintentables.",
compatible: "Compatible",
mainstream: "Popular",
color: "Color",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index bfd1b6eaf0..a45fb64ba4 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -386,6 +386,10 @@ export default withEnglishFallback({
dmCompatHint: "Richiede il driver ODBC DM8 installato sul sistema.",
dmDownload: "Scarica da Dameng",
mongoLegacyHint: "Usa Legacy per server MongoDB meno recenti, specialmente versioni precedenti alla 4.2. Se l'autenticazione fallisce e l'utente è stato creato in admin, imposta DB di Autenticazione su admin.",
+ mongoTlsAllowInvalidCertificates: "Disabilita verifica certificato TLS",
+ mongoTlsAllowInvalidCertificatesHint: "Corrisponde a tlsAllowInvalidCertificates=true e disabilita tutta la validazione del certificato, non solo il controllo dell'hostname. Aumenta il rischio MITM. Di solito è necessario per AWS DocumentDB tramite tunnel SSH.",
+ mongoRetryWrites: "Riprova scritture",
+ mongoRetryWritesHint: "Corrisponde a retryWrites. Disabilitalo per AWS DocumentDB, che non supporta scritture ripetibili.",
compatible: "Compatibile",
mainstream: "Popolare",
color: "Colore",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index 1a91e24466..77d21b587d 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -380,6 +380,10 @@ export default withEnglishFallback({
dmCompatHint: "システムにDM8 ODBCドライバーがインストールされている必要があります。",
dmDownload: "達夢からダウンロード",
mongoLegacyHint: "古いMongoDBサーバー(特に4.2未満)ではレガシーを使用してください。認証に失敗しユーザーがadminで作成された場合は、認証DBをadminに設定してください。",
+ mongoTlsAllowInvalidCertificates: "TLS 証明書検証を無効化",
+ mongoTlsAllowInvalidCertificatesHint: "tlsAllowInvalidCertificates=true に対応し、ホスト名だけでなくすべての証明書検証を無効化します。MITM 攻撃のリスクが高まります。SSH トンネル経由の AWS DocumentDB では通常必要です。",
+ mongoRetryWrites: "書き込み再試行",
+ mongoRetryWritesHint: "retryWrites に対応します。AWS DocumentDB は再試行可能な書き込みをサポートしないため、無効にしてください。",
compatible: "互換",
mainstream: "人気",
color: "色",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index 97c2443a17..975d57bc54 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -387,6 +387,10 @@ export default withEnglishFallback({
dmCompatHint: "Requer o driver ODBC DM8 instalado no seu sistema.",
dmDownload: "Baixar da Dameng",
mongoLegacyHint: "Use Legacy para servidores MongoDB antigos, especialmente versões anteriores à 4.2. Se a autenticação falhar e o usuário foi criado em admin, defina o BD de autenticação como admin.",
+ mongoTlsAllowInvalidCertificates: "Desativar verificação de certificado TLS",
+ mongoTlsAllowInvalidCertificatesHint: "Corresponde a tlsAllowInvalidCertificates=true e desativa toda a validação de certificados, não apenas a do hostname. Aumenta o risco de MITM. Geralmente é necessário para AWS DocumentDB via túnel SSH.",
+ mongoRetryWrites: "Repetir gravações",
+ mongoRetryWritesHint: "Corresponde a retryWrites. Desative para AWS DocumentDB, que não suporta gravações repetíveis.",
compatible: "Compatível",
mainstream: "Popular",
color: "Cor",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 2187216a5e..e9d0e5de96 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -387,6 +387,10 @@ export default withEnglishFallback({
dmCompatHint: "需要在系统上安装达梦 DM8 ODBC 驱动程序。",
dmDownload: "前往达梦官网下载",
mongoLegacyHint: "旧版 MongoDB(尤其是 4.2 以下版本)请选择旧版兼容。若账号创建在 admin 且认证失败,请将认证库设为 admin。",
+ mongoTlsAllowInvalidCertificates: "禁用 TLS 证书校验",
+ mongoTlsAllowInvalidCertificatesHint: "对应 tlsAllowInvalidCertificates=true,会禁用全部证书校验(不仅是主机名),存在中间人攻击风险。经 SSH 隧道访问 AWS DocumentDB 时通常需要开启。",
+ mongoRetryWrites: "重试写入",
+ mongoRetryWritesHint: "对应 retryWrites。AWS DocumentDB 不支持可重试写入,请关闭。",
compatible: "兼容",
mainstream: "主流",
color: "颜色",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index 052890a648..2e5fd5d91c 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -387,6 +387,10 @@ export default withEnglishFallback({
dmCompatHint: "需要在系統上安裝達夢 DM8 ODBC 驅動程式。",
dmDownload: "前往達夢官網下載",
mongoLegacyHint: "舊版 MongoDB(尤其是 4.2 以下版本)請選擇舊版相容。若帳號建立在 admin 且認證失敗,請將認證庫設為 admin。",
+ mongoTlsAllowInvalidCertificates: "停用 TLS 憑證校驗",
+ mongoTlsAllowInvalidCertificatesHint: "對應 tlsAllowInvalidCertificates=true,會停用全部憑證校驗(不只是主機名),存在中間人攻擊風險。經 SSH 隧道存取 AWS DocumentDB 時通常需要開啟。",
+ mongoRetryWrites: "重試寫入",
+ mongoRetryWritesHint: "對應 retryWrites。AWS DocumentDB 不支援可重試寫入,請關閉。",
compatible: "相容",
mainstream: "熱門",
color: "顏色",
diff --git a/apps/desktop/src/lib/mongoConnectionOptions.ts b/apps/desktop/src/lib/mongoConnectionOptions.ts
index 49bccc8998..7fb36fc585 100644
--- a/apps/desktop/src/lib/mongoConnectionOptions.ts
+++ b/apps/desktop/src/lib/mongoConnectionOptions.ts
@@ -14,7 +14,42 @@ export function setMongoUrlParam(urlParams: string | undefined, key: string, val
return params.toString();
}
+export function mongoUrlParamIsTrue(urlParams: string | undefined, key: string, defaultValue = false): boolean {
+ const value = mongoUrlParam(urlParams, key);
+ if (!value) return defaultValue;
+ return ["true", "1", "yes"].includes(value.toLowerCase());
+}
+
+export function setMongoUrlParamBoolean(urlParams: string | undefined, key: string, value: boolean, defaultWhenEnabled = false): string {
+ if (value === defaultWhenEnabled) {
+ return setMongoUrlParam(urlParams, key, "");
+ }
+ return setMongoUrlParam(urlParams, key, value ? "true" : "false");
+}
+
+export function normalizeMongoTlsFormState(ssl: boolean, urlParams: string | undefined, caCertPath: string | undefined): { urlParams: string; caCertPath: string } {
+ if (ssl) {
+ const nextCaCertPath = caCertPath?.trim() || "";
+ let nextUrlParams = urlParams || "";
+ if (nextCaCertPath) {
+ nextUrlParams = setMongoUrlParam(nextUrlParams, "tlsCAFile", "");
+ }
+ return { urlParams: nextUrlParams, caCertPath: nextCaCertPath };
+ }
+ const next = setMongoUrlParam(setMongoUrlParam(setMongoUrlParam(urlParams, "tlsAllowInvalidCertificates", ""), "tlsAllowInvalidHostnames", ""), "tlsCAFile", "");
+ return { urlParams: next, caCertPath: "" };
+}
+
export function mongodbAuthFailureHint(message: string): string {
+ const normalizedMessage = message.toLowerCase();
+ if (normalizedMessage.includes("tlsallowinvalidhostnames is an invalid option")) {
+ return `${message}\n\nDBX uses the Rust MongoDB driver (rustls), which does not accept tlsAllowInvalidHostnames in the connection string. Use tlsAllowInvalidCertificates=true instead. This is commonly required for AWS DocumentDB over an SSH tunnel because the local endpoint is 127.0.0.1 while the server certificate is issued for the DocumentDB hostname.`;
+ }
+
+ if (message.includes('certificate not valid for name "127.0.0.1"') || (message.includes("invalid peer certificate") && message.includes("127.0.0.1") && message.includes("docdb"))) {
+ return `${message}\n\nTLS hostname verification failed because the SSH tunnel connects to 127.0.0.1, but the DocumentDB certificate is issued for the cluster hostname. Add tlsAllowInvalidCertificates=true to URL params. Keep tlsCAFile configured so the certificate chain is still trusted when possible.`;
+ }
+
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.`;
}
diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs
index a070099c4d..2d3527cd3c 100644
--- a/crates/dbx-core/src/models/connection.rs
+++ b/crates/dbx-core/src/models/connection.rs
@@ -1110,7 +1110,9 @@ impl ConnectionConfig {
}
DatabaseType::Databend => normalize_bare_mysql_url_params(value),
DatabaseType::Postgres | DatabaseType::Redshift => normalize_postgres_url_params(value, self.ssl),
- DatabaseType::MongoDb => normalize_mongo_url_params(value, self.ssl, !self.username.trim().is_empty()),
+ DatabaseType::MongoDb => {
+ normalize_mongo_url_params(value, self.ssl, !self.username.trim().is_empty(), self.ca_cert_path.trim())
+ }
_ => value.trim_start_matches('?').to_string(),
}
}
@@ -1270,7 +1272,7 @@ 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, default_auth_source: bool) -> String {
+fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source: bool, ca_cert_path: &str) -> String {
let value = value.trim_start_matches('?');
let mut parts: Vec = value.split('&').filter(|part| !part.is_empty()).map(str::to_string).collect();
@@ -1279,6 +1281,29 @@ fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source:
parts.insert(0, "tls=true".to_string());
}
+ normalize_mongo_tls_compat_params(&mut parts);
+
+ if !force_tls {
+ parts.retain(|part| {
+ !url_param_key_is(part, "tlsAllowInvalidCertificates") && !url_param_key_is(part, "tlsCAFile")
+ });
+ }
+
+ let existing_tls_ca_file = if ca_cert_path.is_empty() {
+ parts.iter().find(|part| url_param_key_is(part, "tlsCAFile")).cloned()
+ } else {
+ None
+ };
+ parts.retain(|part| !url_param_key_is(part, "tlsCAFile"));
+
+ if force_tls {
+ if !ca_cert_path.is_empty() {
+ parts.push(format!("tlsCAFile={}", encode_mongo_tls_file_path(ca_cert_path)));
+ } else if let Some(part) = existing_tls_ca_file {
+ parts.push(part);
+ }
+ }
+
if default_auth_source && !parts.iter().any(|part| url_param_key_is(part, "authSource")) {
parts.push("authSource=admin".to_string());
}
@@ -1286,6 +1311,29 @@ fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source:
parts.join("&")
}
+/// The Rust MongoDB driver uses rustls by default, which does not accept
+/// `tlsAllowInvalidHostnames` in the connection string. Map it to the
+/// supported `tlsAllowInvalidCertificates` option instead.
+fn normalize_mongo_tls_compat_params(parts: &mut Vec) {
+ let allow_invalid_hostnames =
+ parts.iter().any(|part| url_param_key_is(part, "tlsAllowInvalidHostnames") && mongo_url_param_is_truthy(part));
+ parts.retain(|part| !url_param_key_is(part, "tlsAllowInvalidHostnames"));
+ if allow_invalid_hostnames && !parts.iter().any(|part| url_param_key_is(part, "tlsAllowInvalidCertificates")) {
+ parts.push("tlsAllowInvalidCertificates=true".to_string());
+ }
+}
+
+fn mongo_url_param_is_truthy(part: &str) -> bool {
+ let Some((_, value)) = part.split_once('=') else {
+ return true;
+ };
+ matches!(percent_decode_str(value).decode_utf8_lossy().trim().to_ascii_lowercase().as_str(), "true" | "1" | "yes")
+}
+
+fn encode_mongo_tls_file_path(path: &str) -> String {
+ utf8_percent_encode(path, NON_ALPHANUMERIC).to_string()
+}
+
fn mongo_uri_db_part_for_suffix<'a>(db_part: &'a str, suffix: &str) -> &'a str {
if db_part.is_empty() && !suffix.is_empty() {
"/"
@@ -2495,6 +2543,56 @@ mod tests {
assert_eq!(config.connection_url(), "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin");
}
+ #[test]
+ fn mongodb_form_tls_ca_cert_adds_tls_ca_file_param() {
+ let mut config = mongodb_config("root", "secret", Some("admin"));
+ config.ssl = true;
+ config.ca_cert_path = "/tmp/mongo-ca.pem".to_string();
+
+ assert_eq!(
+ config.connection_url(),
+ "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&tlsCAFile=%2Ftmp%2Fmongo%2Dca%2Epem&authSource=admin"
+ );
+ }
+
+ #[test]
+ fn mongodb_form_tls_cert_params_replace_existing_url_values() {
+ let mut config = mongodb_config("root", "secret", Some("admin"));
+ config.ssl = true;
+ config.ca_cert_path = "/tmp/new-ca.pem".to_string();
+ config.url_params = Some("tlsCAFile=%2Fold-ca.pem&authSource=admin".to_string());
+
+ assert_eq!(
+ config.connection_url(),
+ "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin&tlsCAFile=%2Ftmp%2Fnew%2Dca%2Epem"
+ );
+ }
+
+ #[test]
+ fn mongodb_form_tls_preserves_legacy_tls_ca_file_in_url_params() {
+ let mut config = mongodb_config("root", "secret", Some("admin"));
+ config.ssl = true;
+ config.url_params = Some("tlsCAFile=%2Ftmp%2Flegacy-ca.pem&authSource=admin".to_string());
+
+ assert_eq!(
+ config.connection_url(),
+ "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin&tlsCAFile=%2Ftmp%2Flegacy-ca.pem"
+ );
+ }
+
+ #[test]
+ fn mongodb_form_tls_allow_invalid_hostnames_maps_to_allow_invalid_certificates() {
+ let mut config = mongodb_config("root", "secret", Some("admin"));
+ config.ssl = true;
+ config.url_params = Some("replicaSet=rs0&tlsAllowInvalidHostnames=true".to_string());
+
+ assert_eq!(
+ config.connection_url(),
+ "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&replicaSet=rs0&tlsAllowInvalidCertificates=true&authSource=admin"
+ );
+ assert!(!config.connection_url().contains("tlsAllowInvalidHostnames"));
+ }
+
#[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 697a47e634..0868b3fde6 100644
--- a/packages/app-tests/mongoConnectionOptions.test.ts
+++ b/packages/app-tests/mongoConnectionOptions.test.ts
@@ -1,6 +1,6 @@
import { strict as assert } from "node:assert";
import { test } from "vitest";
-import { mongoUrlParam, setMongoUrlParam, mongodbAuthFailureHint } from "../../apps/desktop/src/lib/mongoConnectionOptions.ts";
+import { mongoUrlParam, mongoUrlParamIsTrue, normalizeMongoTlsFormState, setMongoUrlParam, setMongoUrlParamBoolean, mongodbAuthFailureHint } from "../../apps/desktop/src/lib/mongoConnectionOptions.ts";
test("reads MongoDB authSource from URL params", () => {
assert.equal(mongoUrlParam("?replicaSet=rs0&authSource=admin", "authSource"), "admin");
@@ -14,6 +14,43 @@ test("removes empty MongoDB authSource from URL params", () => {
assert.equal(setMongoUrlParam("replicaSet=rs0&authSource=admin", "authSource", ""), "replicaSet=rs0");
});
+test("reads MongoDB boolean URL params", () => {
+ assert.equal(mongoUrlParamIsTrue("tlsAllowInvalidCertificates=true", "tlsAllowInvalidCertificates"), true);
+ assert.equal(mongoUrlParamIsTrue("retryWrites=false", "retryWrites", true), false);
+ assert.equal(mongoUrlParamIsTrue("", "retryWrites", true), true);
+});
+
+test("sets MongoDB boolean URL params", () => {
+ assert.equal(setMongoUrlParamBoolean("replicaSet=rs0", "retryWrites", false, true), "replicaSet=rs0&retryWrites=false");
+ assert.equal(setMongoUrlParamBoolean("replicaSet=rs0&retryWrites=false", "retryWrites", true, true), "replicaSet=rs0");
+ assert.equal(setMongoUrlParamBoolean("", "tlsAllowInvalidCertificates", true), "tlsAllowInvalidCertificates=true");
+});
+
+test("clears MongoDB TLS form state when SSL is disabled", () => {
+ assert.deepEqual(
+ normalizeMongoTlsFormState(false, "replicaSet=rs0&tlsAllowInvalidCertificates=true&tlsAllowInvalidHostnames=true&tlsCAFile=%2Ftmp%2Fca.pem", "/tmp/ca.pem"),
+ { urlParams: "replicaSet=rs0", caCertPath: "" },
+ );
+ assert.deepEqual(normalizeMongoTlsFormState(true, "replicaSet=rs0", " /tmp/ca.pem "), {
+ urlParams: "replicaSet=rs0",
+ caCertPath: "/tmp/ca.pem",
+ });
+});
+
+test("preserves MongoDB tlsCAFile in URL params when caCertPath is empty", () => {
+ assert.deepEqual(normalizeMongoTlsFormState(true, "replicaSet=rs0&tlsCAFile=%2Ftmp%2Flegacy-ca.pem", ""), {
+ urlParams: "replicaSet=rs0&tlsCAFile=%2Ftmp%2Flegacy-ca.pem",
+ caCertPath: "",
+ });
+});
+
+test("form caCertPath overrides tlsCAFile in URL params when set", () => {
+ assert.deepEqual(normalizeMongoTlsFormState(true, "replicaSet=rs0&tlsCAFile=%2Ftmp%2Flegacy-ca.pem", "/tmp/explicit-ca.pem"), {
+ urlParams: "replicaSet=rs0",
+ caCertPath: "/tmp/explicit-ca.pem",
+ });
+});
+
test("adds a MongoDB authSource hint for legacy authentication failures", () => {
const message = "Agent RPC error: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='rwuser', source='gray_lite_twin_fat'}";
@@ -52,3 +89,16 @@ test("adds a MongoDB listDatabases permission hint", () => {
assert.match(mongodbAuthFailureHint(message), /does not have permission to run listDatabases/);
});
+
+test("adds a MongoDB rustls hint for tlsAllowInvalidHostnames", () => {
+ const message = "MongoDB connection failed: Kind: An invalid argument was provided: tlsAllowInvalidHostnames is an invalid option";
+
+ assert.match(mongodbAuthFailureHint(message), /tlsAllowInvalidCertificates=true/);
+});
+
+test("adds a MongoDB DocumentDB SSH tunnel hostname hint", () => {
+ const message =
+ 'MongoDB connection failed: Kind: Server selection timeout: invalid peer certificate: certificate not valid for name "127.0.0.1"; certificate is only valid for DnsName("docdb-2023-06-13-09-23-15.ctofbpmjm2lc.ap-northeast-1.docdb.amazonaws.com")';
+
+ assert.match(mongodbAuthFailureHint(message), /tlsAllowInvalidCertificates=true/);
+});