diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue
index a827d9e9d..4f755a578 100644
--- a/apps/desktop/src/components/connection/ConnectionDialog.vue
+++ b/apps/desktop/src/components/connection/ConnectionDialog.vue
@@ -17,6 +17,7 @@ import * as api from "@/lib/api";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import { applyParsedConnectionUrl, parseConnectionUrl } from "@/lib/connectionUrl";
import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connectionPresentation";
+import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions";
import { showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint";
import { ArrowLeft, ChevronRight, Copy, ExternalLink, FolderOpen, Grid3X3, Link2, List, Search } from "lucide-vue-next";
@@ -533,6 +534,18 @@ const testResultMessage = computed(() => {
if (!testResult.value) return "";
return testResult.value.ok ? t("connection.testSuccess") : testResult.value.message;
});
+const mongoAuthDatabase = computed({
+ get: () => mongoUrlParam(form.value.url_params, "authSource"),
+ set: (value: string) => {
+ form.value.url_params = setMongoUrlParam(form.value.url_params, "authSource", value);
+ },
+});
+const mongoAuthMechanism = computed({
+ get: () => mongoUrlParam(form.value.url_params, "authMechanism") || "default",
+ set: (value: string) => {
+ form.value.url_params = setMongoUrlParam(form.value.url_params, "authMechanism", value === "default" ? "" : value);
+ },
+});
function goToConnectionStep(value = selectedType.value) {
if (value !== selectedType.value) {
@@ -565,7 +578,7 @@ async function testConnection() {
testResult.value = { ok: true, message: msg };
} catch (e: any) {
if (runId !== testRunId) return;
- testResult.value = { ok: false, message: String(e) };
+ testResult.value = { ok: false, message: mongodbAuthFailureHint(String(e)) };
} finally {
if (runId === testRunId) {
isTesting.value = false;
@@ -703,13 +716,13 @@ async function save() {
emit("connectSucceeded", config.name);
})
.catch((e: any) => {
- emit("connectFailed", String(e?.message || e));
+ emit("connectFailed", mongodbAuthFailureHint(String(e?.message || e)));
});
return;
}
open.value = false;
} catch (e: any) {
- testResult.value = { ok: false, message: String(e?.message || e) };
+ testResult.value = { ok: false, message: mongodbAuthFailureHint(String(e?.message || e)) };
} finally {
isSaving.value = false;
}
@@ -1196,13 +1209,34 @@ function openExternalUrl(url: string) {
-
+
+
+
+
+
+
+
+
+
("connect", connect_params).await?;
+ client
+ .call::
("connect", connect_params)
+ .await
+ .map_err(|err| mongo_legacy_error_with_auth_hint(&err))?;
PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client)))
} else {
return Err(native_err);
@@ -466,6 +469,23 @@ pub fn agent_connect_params(config: &ConnectionConfig, host: &str, port: u16, da
})
}
+pub fn mongo_legacy_error_with_auth_hint(err: &str) -> String {
+ let Some(source_start) = err.find("source='") else {
+ return err.to_string();
+ };
+ if !err.contains("Exception authenticating MongoCredential") || err.contains("Current authentication database:") {
+ return err.to_string();
+ }
+ let source = &err[source_start + "source='".len()..];
+ let Some(source_end) = source.find('\'') else {
+ return err.to_string();
+ };
+ let source = &source[..source_end];
+ format!(
+ "{err}\n\nCurrent authentication database: {source}. If this user was created in admin, set Authentication database to admin or add authSource=admin to URL params."
+ )
+}
+
fn oracle_jdbc_connection_string(config: &ConnectionConfig, host: &str, port: u16, database: &str) -> String {
let database = database.trim();
if database.is_empty() {
@@ -602,6 +622,16 @@ mod tests {
assert_eq!(params["connection_string"], "mongodb://mongouser:secret@172.22.4.42:27017/RestCloud%5FV45PUB%5FGateway?authSource=admin&authMechanism=SCRAM-SHA-1");
}
+ #[test]
+ fn mongo_legacy_auth_error_adds_auth_source_hint() {
+ let err = "Agent RPC error: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='rwuser', source='gray_lite_twin_fat'}";
+
+ assert_eq!(
+ super::mongo_legacy_error_with_auth_hint(err),
+ "Agent RPC error: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='rwuser', source='gray_lite_twin_fat'}\n\nCurrent authentication database: gray_lite_twin_fat. If this user was created in admin, set Authentication database to admin or add authSource=admin to URL params."
+ );
+ }
+
#[test]
fn agent_connect_params_build_oracle_service_connection_string() {
let mut config = mysql_config(Some("ORCLPDB1"));
diff --git a/packages/app-tests/mongoConnectionOptions.test.ts b/packages/app-tests/mongoConnectionOptions.test.ts
new file mode 100644
index 000000000..1fe9c1d4c
--- /dev/null
+++ b/packages/app-tests/mongoConnectionOptions.test.ts
@@ -0,0 +1,29 @@
+import { strict as assert } from "node:assert";
+import test from "node:test";
+import {
+ mongoUrlParam,
+ setMongoUrlParam,
+ mongodbAuthFailureHint,
+} from "../../apps/desktop/src/lib/mongoConnectionOptions.ts";
+
+test("reads MongoDB authSource from URL params", () => {
+ assert.equal(mongoUrlParam("?replicaSet=rs0&authSource=admin", "authSource"), "admin");
+});
+
+test("sets MongoDB authSource while preserving other URL params", () => {
+ assert.equal(setMongoUrlParam("replicaSet=rs0", "authSource", "admin"), "replicaSet=rs0&authSource=admin");
+});
+
+test("removes empty MongoDB authSource from URL params", () => {
+ assert.equal(setMongoUrlParam("replicaSet=rs0&authSource=admin", "authSource", ""), "replicaSet=rs0");
+});
+
+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'}";
+
+ assert.equal(
+ mongodbAuthFailureHint(message),
+ "Agent RPC error: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='rwuser', source='gray_lite_twin_fat'}\n\nCurrent authentication database: gray_lite_twin_fat. If this user was created in admin, set Authentication database to admin or add authSource=admin to URL params.",
+ );
+});
diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs
index 28303e7f7..802943242 100644
--- a/src-tauri/src/commands/connection.rs
+++ b/src-tauri/src/commands/connection.rs
@@ -3,7 +3,8 @@ use tauri::State;
pub use dbx_core::connection::{
agent_connect_params, connection_url_for_endpoint, expand_tilde, metadata_connection_config,
- probe_connection_endpoint, redacted_connection_url_for_endpoint, AppState, MysqlMode, PoolKind,
+ mongo_legacy_error_with_auth_hint, probe_connection_endpoint, redacted_connection_url_for_endpoint, AppState,
+ MysqlMode, PoolKind,
};
use dbx_core::database_capabilities;
use dbx_core::db;
@@ -167,7 +168,8 @@ pub async fn test_connection(state: State<'_, Arc>, config: Connection
let mut client = am.spawn(&config.db_type, config.driver_profile.as_deref()).await?;
client
.call::("connect", mongo_legacy_connect_params(&config, &host, port))
- .await?;
+ .await
+ .map_err(|err| mongo_legacy_error_with_auth_hint(&err))?;
client.call::("disconnect", serde_json::json!({})).await.ok();
Ok("Connection successful (via legacy driver)".to_string())
} else {