+ {{ t("connection.oidcBrowserAuthHint") }}
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 57fb45e09d..63a2ecdbe3 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -473,6 +473,7 @@ export default {
authDatabasePlaceholder: "Optional, often admin",
authMechanism: "Auth Mechanism",
authMechanismDefault: "Default",
+ oidcBrowserAuthHint: "Authentication opens in your system browser. No database password is required.",
driverMode: "Driver",
mongoDriverAuto: "Auto",
mongoDriverLegacy: "Legacy",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index 0be6f1b4f3..0cec156bf0 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -475,6 +475,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "Opcional, normalmente admin",
authMechanism: "Mecanismo de autenticación",
authMechanismDefault: "Predeterminado",
+ oidcBrowserAuthHint: "La autenticación se completa en el navegador del sistema. No se requiere una contraseña de base de datos.",
driverMode: "Controlador",
mongoDriverAuto: "Automático",
mongoDriverLegacy: "Legacy",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index 0ad56071a9..165ab6a92e 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -474,6 +474,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "Opzionale, spesso admin",
authMechanism: "Meccanismo di Autenticazione",
authMechanismDefault: "Predefinito",
+ oidcBrowserAuthHint: "L'autenticazione viene completata nel browser di sistema. Non è richiesta una password del database.",
driverMode: "Driver",
mongoDriverAuto: "Automatico",
mongoDriverLegacy: "Legacy",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index 634867e3fb..3f47ad1513 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -474,6 +474,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "任意、通常は admin",
authMechanism: "認証方式",
authMechanismDefault: "デフォルト",
+ oidcBrowserAuthHint: "接続時にシステムブラウザーで認証します。データベースのパスワードは不要です",
driverMode: "ドライバー",
mongoDriverAuto: "自動",
mongoDriverLegacy: "レガシー",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index 0b060552d9..915b233b7d 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -467,6 +467,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "선택, 보통 admin",
authMechanism: "인증 방식",
authMechanismDefault: "기본",
+ oidcBrowserAuthHint: "연결할 때 시스템 브라우저에서 인증을 완료하며 데이터베이스 비밀번호는 필요하지 않습니다.",
driverMode: "드라이버",
mongoDriverAuto: "자동",
mongoDriverLegacy: "레거시",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index 84f7b79768..9a7ab49559 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -475,6 +475,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "Opcional, geralmente admin",
authMechanism: "Mecanismo de Autenticação",
authMechanismDefault: "Padrão",
+ oidcBrowserAuthHint: "A autenticação é concluída no navegador do sistema. Não é necessária uma senha do banco de dados.",
driverMode: "Driver",
mongoDriverAuto: "Automático",
mongoDriverLegacy: "Legacy",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index f801aa0f17..a86b52ed40 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -398,6 +398,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "可选,通常为 admin",
authMechanism: "认证机制",
authMechanismDefault: "默认",
+ oidcBrowserAuthHint: "连接时将在系统浏览器中完成身份认证,无需填写数据库密码",
driverMode: "驱动",
mongoDriverAuto: "自动",
mongoDriverLegacy: "旧版兼容",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index 00d724fae8..1af981f0ee 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -475,6 +475,7 @@ export default withEnglishFallback({
authDatabasePlaceholder: "可選,通常為 admin",
authMechanism: "認證機制",
authMechanismDefault: "預設",
+ oidcBrowserAuthHint: "連線時將在系統瀏覽器中完成身分驗證,無需填寫資料庫密碼",
driverMode: "驅動",
mongoDriverAuto: "自動",
mongoDriverLegacy: "舊版相容",
diff --git a/apps/desktop/src/lib/__tests__/connection/connectionAttemptTimeout.spec.ts b/apps/desktop/src/lib/__tests__/connection/connectionAttemptTimeout.spec.ts
index a3899047b7..3659ea0709 100644
--- a/apps/desktop/src/lib/__tests__/connection/connectionAttemptTimeout.spec.ts
+++ b/apps/desktop/src/lib/__tests__/connection/connectionAttemptTimeout.spec.ts
@@ -1,5 +1,14 @@
import { describe, expect, it } from "vitest";
-import { ACCESS_AGENT_MIN_CONNECT_TIMEOUT_SECS, AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS, connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs, CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS } from "@/lib/connection/connectionAttemptTimeout";
+import {
+ ACCESS_AGENT_MIN_CONNECT_TIMEOUT_SECS,
+ AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS,
+ connectionAttemptOriginalErrorMessage,
+ connectionAttemptTimeoutMessage,
+ connectionAttemptTimeoutMs,
+ CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS,
+ MONGO_LEGACY_FALLBACK_TIMEOUT_BUFFER_MS,
+ MONGO_OIDC_BROWSER_AUTH_TIMEOUT_MS,
+} from "@/lib/connection/connectionAttemptTimeout";
describe("connectionAttemptTimeout", () => {
it("uses a 10s default for regular database connections", () => {
@@ -42,6 +51,21 @@ describe("connectionAttemptTimeout", () => {
expect(connectionAttemptTimeoutMs({ db_type: "access", connect_timeout_secs: 45, transport_layers: [] })).toBe(47_000);
});
+ it("keeps the legacy fallback buffer for regular MongoDB connections", () => {
+ expect(connectionAttemptTimeoutMs({ db_type: "mongodb", connect_timeout_secs: 5, transport_layers: [] })).toBe(5_000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS + MONGO_LEGACY_FALLBACK_TIMEOUT_BUFFER_MS);
+ });
+
+ it("allows the MongoDB OIDC browser flow to use its full five-minute budget", () => {
+ expect(
+ connectionAttemptTimeoutMs({
+ db_type: "mongodb",
+ connect_timeout_secs: 5,
+ transport_layers: [],
+ connection_string: "mongodb+srv://cluster.example.com/app?authMechanism=MONGODB-OIDC&authSource=%24external",
+ }),
+ ).toBe(5_000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS + MONGO_OIDC_BROWSER_AUTH_TIMEOUT_MS);
+ });
+
it("adds HTTP tunnel setup and tunneled endpoint probe budgets", () => {
expect(
connectionAttemptTimeoutMs({
diff --git a/apps/desktop/src/lib/connection/connectionAttemptTimeout.ts b/apps/desktop/src/lib/connection/connectionAttemptTimeout.ts
index 0605245790..b0697436c4 100644
--- a/apps/desktop/src/lib/connection/connectionAttemptTimeout.ts
+++ b/apps/desktop/src/lib/connection/connectionAttemptTimeout.ts
@@ -1,7 +1,9 @@
import type { ConnectionConfig, DatabaseType, TransportLayerConfig, TunnelProfile } from "@/types/database";
+import { mongoConnectionUsesOidc } from "@/lib/mongo/mongoConnectionOptions";
export const CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS = 2_000;
export const MONGO_LEGACY_FALLBACK_TIMEOUT_BUFFER_MS = 30_000;
+export const MONGO_OIDC_BROWSER_AUTH_TIMEOUT_MS = 5 * 60_000;
export const AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS = 30;
export const ACCESS_AGENT_MIN_CONNECT_TIMEOUT_SECS = 30;
const DEFAULT_CONNECT_TIMEOUT_SECS = 10;
@@ -66,7 +68,7 @@ function resolvedTimeoutLayer(layer: TransportLayerConfig, resolveTunnelProfile?
return { ...profile, id: layer.id, enabled: layer.enabled, profile_id: layer.profile_id } as TransportLayerConfig;
}
-export function connectionAttemptTimeoutMs(config: Pick
& Partial>, resolveTunnelProfile?: TunnelProfileResolver): number {
+export function connectionAttemptTimeoutMs(config: Pick & Partial>, resolveTunnelProfile?: TunnelProfileResolver): number {
const baseTimeoutSecs = positiveSeconds(config.connect_timeout_secs, DEFAULT_CONNECT_TIMEOUT_SECS);
const agentMinTimeoutSecs = config.db_type === "access" ? ACCESS_AGENT_MIN_CONNECT_TIMEOUT_SECS : AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS;
let timeoutSecs = DRIVER_STARTUP_FLOOR_TYPES.has(config.db_type as DatabaseType) ? Math.max(baseTimeoutSecs, agentMinTimeoutSecs) : baseTimeoutSecs;
@@ -83,8 +85,10 @@ export function connectionAttemptTimeoutMs(config: Pick {
+ it("detects OIDC in structured URL params without case sensitivity", () => {
+ expect(mongoConnectionUsesOidc("authSource=%24external&AUTHMECHANISM=mongodb-oidc")).toBe(true);
+ });
+
+ it("detects OIDC in mongodb and mongodb+srv connection strings", () => {
+ expect(mongoConnectionUsesOidc(undefined, "mongodb://localhost/app?authMechanism=MONGODB-OIDC")).toBe(true);
+ expect(mongoConnectionUsesOidc(undefined, "mongodb+srv://cluster.example.com/app?authMechanism=MONGODB%2DOIDC#fragment")).toBe(true);
+ });
+
+ it("does not classify non-OIDC or non-MongoDB URLs as OIDC", () => {
+ expect(mongoConnectionUsesOidc("authMechanism=SCRAM-SHA-256")).toBe(false);
+ expect(mongoConnectionUsesOidc(undefined, "postgres://localhost/app?authMechanism=MONGODB-OIDC")).toBe(false);
+ });
+
+ it("treats a connection string as authoritative over stale form params", () => {
+ expect(mongoConnectionUsesOidc("authMechanism=MONGODB-OIDC", "mongodb://localhost/app?authMechanism=SCRAM-SHA-256")).toBe(false);
+ });
+});
diff --git a/apps/desktop/src/lib/mongo/mongoConnectionOptions.ts b/apps/desktop/src/lib/mongo/mongoConnectionOptions.ts
index de00641f7e..78783a946e 100644
--- a/apps/desktop/src/lib/mongo/mongoConnectionOptions.ts
+++ b/apps/desktop/src/lib/mongo/mongoConnectionOptions.ts
@@ -3,6 +3,20 @@ export function mongoUrlParam(urlParams: string | undefined, key: string): strin
return params.get(key) || "";
}
+export function mongoConnectionUsesOidc(urlParams: string | undefined, connectionString?: string): boolean {
+ const input = connectionString?.trim() || "";
+ if (input) {
+ if (!/^mongodb(?:\+srv)?:\/\//i.test(input)) return false;
+ const queryStart = input.indexOf("?");
+ if (queryStart < 0) return false;
+ const fragmentStart = input.indexOf("#", queryStart + 1);
+ const query = input.slice(queryStart + 1, fragmentStart < 0 ? undefined : fragmentStart);
+ return mongoUrlParamCaseInsensitive(query, "authMechanism").toUpperCase() === "MONGODB-OIDC";
+ }
+
+ return mongoUrlParamCaseInsensitive(urlParams, "authMechanism").toUpperCase() === "MONGODB-OIDC";
+}
+
export function setMongoUrlParam(urlParams: string | undefined, key: string, value: string): string {
const params = parseMongoUrlParams(urlParams);
const normalized = value.trim();
@@ -84,3 +98,11 @@ export function mongodbAuthFailureHint(message: string): string {
function parseMongoUrlParams(urlParams: string | undefined): URLSearchParams {
return new URLSearchParams((urlParams || "").trim().replace(/^\?/, ""));
}
+
+function mongoUrlParamCaseInsensitive(urlParams: string | undefined, key: string): string {
+ const expectedKey = key.toLowerCase();
+ for (const [paramKey, value] of parseMongoUrlParams(urlParams)) {
+ if (paramKey.toLowerCase() === expectedKey) return value;
+ }
+ return "";
+}
diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs
index 220e8de748..01ba6019f4 100644
--- a/crates/dbx-core/src/connection.rs
+++ b/crates/dbx-core/src/connection.rs
@@ -26,6 +26,7 @@ use crate::models::connection::{
database_info_from_protocol_value, parse_jdbc_host_port, parse_mongo_first_host, rewrite_jdbc_url_host,
ConnectionConfig, ConnectionTestResult, DatabaseConnectionInfo, DatabaseType, TransportLayerConfig,
};
+use crate::mongo_oidc::MongoOidcBrowserOpener;
use crate::path_utils::expand_tilde;
use crate::plugins::{PluginDriverSession, PluginRegistry, PluginRuntimeEnv};
use crate::query_cancel::RunningQueries;
@@ -262,6 +263,7 @@ pub struct AppState {
/// AI/元数据从它读取,前端通过状态接口查询。
pub session_credentials: SessionCredentialStore,
metadata_gates: Arc>>>,
+ mongo_oidc_browser_opener: std::sync::RwLock