Skip to content

Commit c4ead5a

Browse files
authored
Merge pull request #268 from datlechin/feat/mongodb-authsource
feat: MongoDB configurable authSource + fix connection field persistence
2 parents d8c2aa3 + 3aea538 commit c4ead5a

9 files changed

Lines changed: 68 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- DuckDB database support — connect to `.duckdb` files, query CSV/Parquet/JSON files via SQL, schema navigation, and DuckDB extension management
13+
- MongoDB configurable auth database (`authSource`) — authenticate against any database instead of hardcoded `admin`
1314

1415
### Fixed
1516

17+
- MongoDB Read Preference, Write Concern, and Redis Database not persisted across app restarts
18+
1619
- Result truncation at 100K rows now reported to UI via `PluginQueryResult.isTruncated` instead of being silently discarded
1720
- DELETE and UPDATE queries using all columns in WHERE clause instead of just the primary key for PostgreSQL, Redshift, MSSQL, and ClickHouse
1821
- SSL/TLS always being enabled for MongoDB, Redis, and ClickHouse connections due to case mismatch in SSL mode string comparison (#249)

Plugins/MongoDBDriverPlugin/MongoDBConnection.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ final class MongoDBConnection: @unchecked Sendable {
5959
private let sslMode: String
6060
private let sslCACertPath: String
6161
private let sslClientCertPath: String
62+
private let authSource: String?
6263
private let readPreference: String?
6364
private let writeConcern: String?
6465

@@ -111,6 +112,7 @@ final class MongoDBConnection: @unchecked Sendable {
111112
sslMode: String = "Disabled",
112113
sslCACertPath: String = "",
113114
sslClientCertPath: String = "",
115+
authSource: String? = nil,
114116
readPreference: String? = nil,
115117
writeConcern: String? = nil
116118
) {
@@ -122,6 +124,7 @@ final class MongoDBConnection: @unchecked Sendable {
122124
self.sslMode = sslMode
123125
self.sslCACertPath = sslCACertPath
124126
self.sslClientCertPath = sslClientCertPath
127+
self.authSource = authSource
125128
self.readPreference = readPreference
126129
self.writeConcern = writeConcern
127130
}
@@ -167,10 +170,13 @@ final class MongoDBConnection: @unchecked Sendable {
167170
uri += "\(encodedHost):\(port)"
168171
uri += database.isEmpty ? "/" : "/\(encodedDb)"
169172

173+
let effectiveAuthSource = authSource.flatMap { $0.isEmpty ? nil : $0 } ?? "admin"
174+
let encodedAuthSource = effectiveAuthSource
175+
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? effectiveAuthSource
170176
var params: [String] = [
171177
"connectTimeoutMS=10000",
172178
"serverSelectionTimeoutMS=10000",
173-
"authSource=admin"
179+
"authSource=\(encodedAuthSource)"
174180
]
175181

176182
let sslEnabled = ["Preferred", "Required", "Verify CA", "Verify Identity"].contains(sslMode)

Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class MongoDBPlugin: NSObject, TableProPlugin, DriverPlugin {
1717
static let iconName = "leaf.fill"
1818
static let defaultPort = 27017
1919
static let additionalConnectionFields: [ConnectionField] = [
20+
ConnectionField(id: "mongoAuthSource", label: "Auth Database", placeholder: "admin"),
2021
ConnectionField(id: "mongoReadPreference", label: "Read Preference", placeholder: "primary"),
2122
ConnectionField(id: "mongoWriteConcern", label: "Write Concern", placeholder: "majority")
2223
]

Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ final class MongoDBPluginDriver: PluginDatabaseDriver {
4040
sslMode: config.additionalFields["sslMode"] ?? "Disabled",
4141
sslCACertPath: config.additionalFields["sslCACertPath"] ?? "",
4242
sslClientCertPath: config.additionalFields["sslClientCertPath"] ?? "",
43+
authSource: config.additionalFields["mongoAuthSource"],
4344
readPreference: config.additionalFields["mongoReadPreference"],
4445
writeConcern: config.additionalFields["mongoWriteConcern"]
4546
)

TablePro/AppDelegate+ConnectionHandler.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ extension AppDelegate {
404404
sslConfig: sslConfig,
405405
color: color,
406406
tagId: tagId,
407+
mongoAuthSource: parsed.authSource,
407408
redisDatabase: parsed.redisDatabase,
408409
oracleServiceName: parsed.oracleServiceName
409410
)

TablePro/Core/Database/DatabaseDriver.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ enum DatabaseDriverFactory {
346346
switch connection.type {
347347
case .mongodb:
348348
fields["sslCACertPath"] = ssl.caCertificatePath
349+
fields["mongoAuthSource"] = connection.mongoAuthSource ?? ""
349350
fields["mongoReadPreference"] = connection.mongoReadPreference ?? ""
350351
fields["mongoWriteConcern"] = connection.mongoWriteConcern ?? ""
351352
case .redis:

TablePro/Core/Storage/ConnectionStorage.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ final class ConnectionStorage {
121121
groupId: connection.groupId,
122122
safeModeLevel: connection.safeModeLevel,
123123
aiPolicy: connection.aiPolicy,
124+
mongoAuthSource: connection.mongoAuthSource,
124125
mongoReadPreference: connection.mongoReadPreference,
125126
mongoWriteConcern: connection.mongoWriteConcern,
126127
redisDatabase: connection.redisDatabase,
@@ -368,6 +369,14 @@ private struct StoredConnection: Codable {
368369
// AI policy
369370
let aiPolicy: String?
370371

372+
// MongoDB-specific
373+
let mongoAuthSource: String?
374+
let mongoReadPreference: String?
375+
let mongoWriteConcern: String?
376+
377+
// Redis-specific
378+
let redisDatabase: Int?
379+
371380
// MSSQL schema
372381
let mssqlSchema: String?
373382

@@ -413,6 +422,14 @@ private struct StoredConnection: Codable {
413422
// AI policy
414423
self.aiPolicy = connection.aiPolicy?.rawValue
415424

425+
// MongoDB-specific
426+
self.mongoAuthSource = connection.mongoAuthSource
427+
self.mongoReadPreference = connection.mongoReadPreference
428+
self.mongoWriteConcern = connection.mongoWriteConcern
429+
430+
// Redis-specific
431+
self.redisDatabase = connection.redisDatabase
432+
416433
// MSSQL schema
417434
self.mssqlSchema = connection.mssqlSchema
418435

@@ -431,7 +448,9 @@ private struct StoredConnection: Codable {
431448
case color, tagId, groupId
432449
case safeModeLevel
433450
case isReadOnly // Legacy key for migration reading only
434-
case aiPolicy, mssqlSchema, oracleServiceName, startupCommands
451+
case aiPolicy
452+
case mongoAuthSource, mongoReadPreference, mongoWriteConcern, redisDatabase
453+
case mssqlSchema, oracleServiceName, startupCommands
435454
}
436455

437456
func encode(to encoder: Encoder) throws {
@@ -460,6 +479,10 @@ private struct StoredConnection: Codable {
460479
try container.encodeIfPresent(groupId, forKey: .groupId)
461480
try container.encode(safeModeLevel, forKey: .safeModeLevel)
462481
try container.encodeIfPresent(aiPolicy, forKey: .aiPolicy)
482+
try container.encodeIfPresent(mongoAuthSource, forKey: .mongoAuthSource)
483+
try container.encodeIfPresent(mongoReadPreference, forKey: .mongoReadPreference)
484+
try container.encodeIfPresent(mongoWriteConcern, forKey: .mongoWriteConcern)
485+
try container.encodeIfPresent(redisDatabase, forKey: .redisDatabase)
463486
try container.encodeIfPresent(mssqlSchema, forKey: .mssqlSchema)
464487
try container.encodeIfPresent(oracleServiceName, forKey: .oracleServiceName)
465488
try container.encodeIfPresent(startupCommands, forKey: .startupCommands)
@@ -506,6 +529,10 @@ private struct StoredConnection: Codable {
506529
safeModeLevel = wasReadOnly ? SafeModeLevel.readOnly.rawValue : SafeModeLevel.silent.rawValue
507530
}
508531
aiPolicy = try container.decodeIfPresent(String.self, forKey: .aiPolicy)
532+
mongoAuthSource = try container.decodeIfPresent(String.self, forKey: .mongoAuthSource)
533+
mongoReadPreference = try container.decodeIfPresent(String.self, forKey: .mongoReadPreference)
534+
mongoWriteConcern = try container.decodeIfPresent(String.self, forKey: .mongoWriteConcern)
535+
redisDatabase = try container.decodeIfPresent(Int.self, forKey: .redisDatabase)
509536
mssqlSchema = try container.decodeIfPresent(String.self, forKey: .mssqlSchema)
510537
oracleServiceName = try container.decodeIfPresent(String.self, forKey: .oracleServiceName)
511538
startupCommands = try container.decodeIfPresent(String.self, forKey: .startupCommands)
@@ -550,6 +577,10 @@ private struct StoredConnection: Codable {
550577
groupId: parsedGroupId,
551578
safeModeLevel: SafeModeLevel(rawValue: safeModeLevel) ?? .silent,
552579
aiPolicy: parsedAIPolicy,
580+
mongoAuthSource: mongoAuthSource,
581+
mongoReadPreference: mongoReadPreference,
582+
mongoWriteConcern: mongoWriteConcern,
583+
redisDatabase: redisDatabase,
553584
mssqlSchema: mssqlSchema,
554585
oracleServiceName: oracleServiceName,
555586
startupCommands: startupCommands

TablePro/Models/Connection/DatabaseConnection.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ struct DatabaseConnection: Identifiable, Hashable {
405405
var groupId: UUID?
406406
var safeModeLevel: SafeModeLevel
407407
var aiPolicy: AIConnectionPolicy?
408+
var mongoAuthSource: String?
408409
var mongoReadPreference: String?
409410
var mongoWriteConcern: String?
410411
var redisDatabase: Int?
@@ -427,6 +428,7 @@ struct DatabaseConnection: Identifiable, Hashable {
427428
groupId: UUID? = nil,
428429
safeModeLevel: SafeModeLevel = .silent,
429430
aiPolicy: AIConnectionPolicy? = nil,
431+
mongoAuthSource: String? = nil,
430432
mongoReadPreference: String? = nil,
431433
mongoWriteConcern: String? = nil,
432434
redisDatabase: Int? = nil,
@@ -448,6 +450,7 @@ struct DatabaseConnection: Identifiable, Hashable {
448450
self.groupId = groupId
449451
self.safeModeLevel = safeModeLevel
450452
self.aiPolicy = aiPolicy
453+
self.mongoAuthSource = mongoAuthSource
451454
self.mongoReadPreference = mongoReadPreference
452455
self.mongoWriteConcern = mongoWriteConcern
453456
self.redisDatabase = redisDatabase

TablePro/Views/Connection/ConnectionFormView.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ struct ConnectionFormView: View {
7272
@State private var aiPolicy: AIConnectionPolicy?
7373

7474
// MongoDB-specific settings
75+
@State private var mongoAuthSource: String = ""
7576
@State private var mongoReadPreference: String = ""
7677
@State private var mongoWriteConcern: String = ""
7778

@@ -572,6 +573,11 @@ struct ConnectionFormView: View {
572573
Form {
573574
if type == .mongodb {
574575
Section("MongoDB") {
576+
TextField(
577+
String(localized: "Auth Database"),
578+
text: $mongoAuthSource,
579+
prompt: Text("admin")
580+
)
575581
Picker(String(localized: "Read Preference"), selection: $mongoReadPreference) {
576582
Text(String(localized: "Default")).tag("")
577583
Text("Primary").tag("primary")
@@ -813,9 +819,15 @@ struct ConnectionFormView: View {
813819
aiPolicy = existing.aiPolicy
814820

815821
// Load MongoDB settings
822+
mongoAuthSource = existing.mongoAuthSource ?? ""
816823
mongoReadPreference = existing.mongoReadPreference ?? ""
817824
mongoWriteConcern = existing.mongoWriteConcern ?? ""
818825

826+
// Load Redis settings
827+
if existing.type == .redis, let rdb = existing.redisDatabase {
828+
database = String(rdb)
829+
}
830+
819831
// Load MSSQL settings
820832
mssqlSchema = existing.mssqlSchema ?? "dbo"
821833

@@ -884,8 +896,10 @@ struct ConnectionFormView: View {
884896
groupId: selectedGroupId,
885897
safeModeLevel: safeModeLevel,
886898
aiPolicy: aiPolicy,
899+
mongoAuthSource: mongoAuthSource.isEmpty ? nil : mongoAuthSource,
887900
mongoReadPreference: mongoReadPreference.isEmpty ? nil : mongoReadPreference,
888901
mongoWriteConcern: mongoWriteConcern.isEmpty ? nil : mongoWriteConcern,
902+
redisDatabase: type == .redis ? (Int(database) ?? 0) : nil,
889903
mssqlSchema: mssqlSchema.isEmpty ? nil : mssqlSchema,
890904
oracleServiceName: oracleServiceName.isEmpty ? nil : oracleServiceName,
891905
startupCommands: startupCommands.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
@@ -1015,8 +1029,10 @@ struct ConnectionFormView: View {
10151029
color: connectionColor,
10161030
tagId: selectedTagId,
10171031
groupId: selectedGroupId,
1032+
mongoAuthSource: mongoAuthSource.isEmpty ? nil : mongoAuthSource,
10181033
mongoReadPreference: mongoReadPreference.isEmpty ? nil : mongoReadPreference,
10191034
mongoWriteConcern: mongoWriteConcern.isEmpty ? nil : mongoWriteConcern,
1035+
redisDatabase: type == .redis ? (Int(database) ?? 0) : nil,
10201036
mssqlSchema: mssqlSchema.isEmpty ? nil : mssqlSchema,
10211037
oracleServiceName: oracleServiceName.isEmpty ? nil : oracleServiceName,
10221038
startupCommands: startupCommands.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
@@ -1154,6 +1170,9 @@ struct ConnectionFormView: View {
11541170
applySSHAgentSocketPath(parsed.agentSocket ?? "")
11551171
}
11561172
}
1173+
if let authSourceValue = parsed.authSource, !authSourceValue.isEmpty {
1174+
mongoAuthSource = authSourceValue
1175+
}
11571176
if let connectionName = parsed.connectionName, !connectionName.isEmpty {
11581177
name = connectionName
11591178
} else if name.isEmpty {

0 commit comments

Comments
 (0)