Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Fill Column: right-click a column header and choose Fill Column to set one value across all loaded rows. The change is staged like a normal edit, so you review it and Save before it applies, and one undo reverts the whole fill. Not available on primary key columns. (#1304)
- AWS IAM authentication for PostgreSQL and MySQL connections to RDS and Aurora. Pick AWS IAM in the connection's Authentication field and use an access key, a named AWS profile, or SSO. TablePro generates a fresh login token on every connect and reconnect, so you never paste an expiring token, and SSL is required automatically. (#1291)

## [0.44.0] - 2026-05-23

Expand Down
54 changes: 27 additions & 27 deletions Libs/checksums.sha256
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
6c16abade13041111a9551245043b195aa27ce79ce0ab32113962e762f63485f Libs/libbson_arm64.a
9b7d7abb13b36a4856fd8b4a6cc2a2b312c1bb1815ae9891cc30fe605badeb3c Libs/libbson_universal.a
adb2ade8531660c846df3dab6cafc0a6ba15bf4a9d0ae0a1bc8edaca98bf1ffa Libs/libbson_x86_64.a
9b7d7abb13b36a4856fd8b4a6cc2a2b312c1bb1815ae9891cc30fe605badeb3c Libs/libbson.a
b7716e3f295a54feee85c8771332505be2f9a4a430a088d476d60e358d737c9e Libs/libbson.a
36e3a521b8da03bafd0f943c4f3b21c8c573bf9d640c6c9e764c0c3632672849 Libs/libbson_arm64.a
b7716e3f295a54feee85c8771332505be2f9a4a430a088d476d60e358d737c9e Libs/libbson_universal.a
1e502e7fb4edc79639140e18d433a1ed1be2931162daecee71a74d09e9f4c550 Libs/libbson_x86_64.a
9bfd7d7cb4a7ee9823b4c5141e942a8534de63395983388722dc7c98e5d7731e Libs/libcassandra.a
8d7e31145470a339f4f57930831936db30412393a339598deece6f650214865a Libs/libcassandra_arm64.a
9bfd7d7cb4a7ee9823b4c5141e942a8534de63395983388722dc7c98e5d7731e Libs/libcassandra_universal.a
7f1d058c77b66273db2b3867103c19f62ed0518fb38611b178ce04029213d5d8 Libs/libcassandra_x86_64.a
9bfd7d7cb4a7ee9823b4c5141e942a8534de63395983388722dc7c98e5d7731e Libs/libcassandra.a
732adf315bc49f77e2511a9293e49a65e18eb54a3e6d01d8a24eee2d671d2a8a Libs/libcrypto.a
a891a67c2619e2ac1dce64dafc6a24bfde9cabe15312dac6b70a19385664ea84 Libs/libcrypto_arm64.a
732adf315bc49f77e2511a9293e49a65e18eb54a3e6d01d8a24eee2d671d2a8a Libs/libcrypto_universal.a
965ccd38fea5cd97bc878dbf58567e4eed2b2337120f8d46a2da62c094b3c821 Libs/libcrypto_x86_64.a
732adf315bc49f77e2511a9293e49a65e18eb54a3e6d01d8a24eee2d671d2a8a Libs/libcrypto.a
69953f30dbc41fb2d12af2471ccc3eea90c465ab775a18cb3eae502c2fa0dd68 Libs/libduckdb_arm64.a
2af0158001439fc4c4f06a5fabb0a5ca4a66b468c0b2c6e808488a399682dc7a Libs/libduckdb_universal.a
66ed8e2e6ac645c09d698028c772649e3276c21757beb4f2eb6cb6589e426eb3 Libs/libduckdb_x86_64.a
2af0158001439fc4c4f06a5fabb0a5ca4a66b468c0b2c6e808488a399682dc7a Libs/libduckdb.a
d95520ba0e250f7c5847cc9dab4bf8a2656fcefd64b35c859f8fae0d37f2f69f Libs/libduckdb.a
1756e47a21076dbfd3bcfb937964dd0af231017f3adc549fdbc114464b304179 Libs/libduckdb_arm64.a
d95520ba0e250f7c5847cc9dab4bf8a2656fcefd64b35c859f8fae0d37f2f69f Libs/libduckdb_universal.a
aa5dfb4014c4b227d842ca20c2572434784cdad2de324afdc28fa8af83965ecd Libs/libduckdb_x86_64.a
c855b0bf6fb8a2f52175a8e212c88a99ddf02890a1f88239613728c145607915 Libs/libhiredis.a
7e63017fa22c2eb7744eccad13857361a5088aa7b2772ab02cd026c8c7b78341 Libs/libhiredis_arm64.a
fb7a32c2c724cb4f3f880030cb19afbbc7db52121ad8e35e00a2e818da9562cf Libs/libhiredis_ssl.a
f1cfc36a7ab47361e9705fe32b1c919b318f606989478e91a808707d93db55a5 Libs/libhiredis_ssl_arm64.a
fb7a32c2c724cb4f3f880030cb19afbbc7db52121ad8e35e00a2e818da9562cf Libs/libhiredis_ssl_universal.a
7eb76bcb7ad4c10da0a0a5d43de182619f74f11c1ae9096823adc5c85280e34b Libs/libhiredis_ssl_x86_64.a
fb7a32c2c724cb4f3f880030cb19afbbc7db52121ad8e35e00a2e818da9562cf Libs/libhiredis_ssl.a
c855b0bf6fb8a2f52175a8e212c88a99ddf02890a1f88239613728c145607915 Libs/libhiredis_universal.a
5e89a8a3b48590f2c68bdcfc0cfde134145e3156d48264c1fd751dc9ef3be505 Libs/libhiredis_x86_64.a
c855b0bf6fb8a2f52175a8e212c88a99ddf02890a1f88239613728c145607915 Libs/libhiredis.a
b777f7a42766fb08c8e67b2310c67d2d463d77d3554c6092221c3352778622b2 Libs/libmariadb_arm64.a
5326ed729b287ae5dbbcf073aaa70dce29a73c7431e446d5958271af19dac8d8 Libs/libmariadb_universal.a
4f7bbb3d73be178d4211c3bd5b2726b4a12db8b808eaa5212bf8e9eb3c570814 Libs/libmariadb_x86_64.a
5326ed729b287ae5dbbcf073aaa70dce29a73c7431e446d5958271af19dac8d8 Libs/libmariadb.a
06268890fb365085d7f093b6941c507fb8f7fa2754fb22c62331ba8e8ae2068a Libs/libmongoc_arm64.a
b063818886170377f6cd1de714157032e3948e8a9616d3488a503423d8045053 Libs/libmongoc_universal.a
51b08ff457246e3032f1a13306f0e540658e91b1c560a7251ce5087a2ff17be0 Libs/libmongoc_x86_64.a
b063818886170377f6cd1de714157032e3948e8a9616d3488a503423d8045053 Libs/libmongoc.a
bdec9e92f4e94ccb20fcfc4c121aab42de510cd214e23c0e334057de11889b9a Libs/libmariadb.a
1fa33b78f52d4815761aeeec20add208719e4f01e0585e417a4d0d6dc98d3e2d Libs/libmariadb_arm64.a
bdec9e92f4e94ccb20fcfc4c121aab42de510cd214e23c0e334057de11889b9a Libs/libmariadb_universal.a
6ca5ea9b190b515108ba577b1db89458c1ac2f3263f2a4e4edcc94d5a66c5984 Libs/libmariadb_x86_64.a
0d7ddc82dc7327a4b5187ffbc68a1419b5e5ff7b2be7b927e16793eef4d34303 Libs/libmongoc.a
9f4c87916ef65eae43b19d7568dc4fd4dffd884dc0cae15913b90965293339a7 Libs/libmongoc_arm64.a
0d7ddc82dc7327a4b5187ffbc68a1419b5e5ff7b2be7b927e16793eef4d34303 Libs/libmongoc_universal.a
635705c7dc8d689efdee5ec1bd8a8cbd0d09ae20db0869480271a293d492de50 Libs/libmongoc_x86_64.a
3ca491a723b9d9dfc13b815659b44a82253b540dd6b115f03ac68c5154ec26db Libs/libpgcommon.a
5dbf2cb5ef37d8adbf607db82461b36a3fd7037c11d891383e6e918378a33d78 Libs/libpgcommon_arm64.a
3ca491a723b9d9dfc13b815659b44a82253b540dd6b115f03ac68c5154ec26db Libs/libpgcommon_universal.a
4bfad7376aefa866d1ed0b7e54966ec6c9d70dcfed928e1311c20321bf08881c Libs/libpgcommon_x86_64.a
3ca491a723b9d9dfc13b815659b44a82253b540dd6b115f03ac68c5154ec26db Libs/libpgcommon.a
efba529b1ad767de988a58ca2c3fdcc26c38ce79df044a988f41fddbf9fde118 Libs/libpgport.a
813b962c5ae1c317bf6facfe68bd1301fa766768e074f3063fc2e8243213fe13 Libs/libpgport_arm64.a
efba529b1ad767de988a58ca2c3fdcc26c38ce79df044a988f41fddbf9fde118 Libs/libpgport_universal.a
bf71cc776245c0ce44bfd7b0286664d5c9771992fd70ec32a0c27fc669e4422f Libs/libpgport_x86_64.a
efba529b1ad767de988a58ca2c3fdcc26c38ce79df044a988f41fddbf9fde118 Libs/libpgport.a
b86ecf68d2b0dd8aa7712d13607c9584df2297aca4cd651428e8ee974c6bdf80 Libs/libpq.a
70cb70b88130c1c88ccf108e31e17d45dbbc2d10267db7ff33d63305a6a05baf Libs/libpq_arm64.a
b86ecf68d2b0dd8aa7712d13607c9584df2297aca4cd651428e8ee974c6bdf80 Libs/libpq_universal.a
1ce2b45af228915fad05e07f54e96621af7143e199e002e5100777261a7f4a13 Libs/libpq_x86_64.a
b86ecf68d2b0dd8aa7712d13607c9584df2297aca4cd651428e8ee974c6bdf80 Libs/libpq.a
445b51e6fdaa0a0eceb8090e6d552a551ec15d91e4370a4cc356c8f561e8b469 Libs/libssh2.a
166e0e23ce60fd2edcae38b6005de106394f7e2bc922a4944317d6aa576f284c Libs/libssh2_arm64.a
445b51e6fdaa0a0eceb8090e6d552a551ec15d91e4370a4cc356c8f561e8b469 Libs/libssh2_universal.a
76681299c4305273cea62e59cfa366ceb5cc320831b87fd6a06143d342f8b7db Libs/libssh2_x86_64.a
445b51e6fdaa0a0eceb8090e6d552a551ec15d91e4370a4cc356c8f561e8b469 Libs/libssh2.a
3ca208dedf57dbae4f5cb0a22bfbedeba80dc6740d626484d9d815811d64a2aa Libs/libssl.a
b3861975896ebf35255d8c3efccdc59ad39874c9b70fdd710ebd15f0a58c4e10 Libs/libssl_arm64.a
3ca208dedf57dbae4f5cb0a22bfbedeba80dc6740d626484d9d815811d64a2aa Libs/libssl_universal.a
34de647ccd0951095f987591562a5236348bac2d4b3e217877559a7b170cf4e4 Libs/libssl_x86_64.a
3ca208dedf57dbae4f5cb0a22bfbedeba80dc6740d626484d9d815811d64a2aa Libs/libssl.a
071e9853ec4bb1f6a19ed99eb91cfe823e83bad178e1e1997deee414cd0e4dfc Libs/libsybdb.a
38a16ca8a041c1be3ca6d4884f7c5e196d14f60bee80004c8f54a41899c17e0f Libs/libsybdb_arm64.a
071e9853ec4bb1f6a19ed99eb91cfe823e83bad178e1e1997deee414cd0e4dfc Libs/libsybdb_universal.a
e437cf1fab3eaf675bdb5aab4443a891763e5325033ddfe369775bd64a22b57b Libs/libsybdb_x86_64.a
071e9853ec4bb1f6a19ed99eb91cfe823e83bad178e1e1997deee414cd0e4dfc Libs/libsybdb.a
8f8135b8214cfef035b49486a863f891979efc04d97d75e2bc14cb4e28aed233 Libs/libuv.a
beff08628396ffb7c2e23b9f1db08ce92be215fbfd50c6e62088e216d73a0897 Libs/libuv_arm64.a
8f8135b8214cfef035b49486a863f891979efc04d97d75e2bc14cb4e28aed233 Libs/libuv_universal.a
2592a74df696709dcc631e9ad48894763157e9c5a34f0cb6a23a4036bce0c472 Libs/libuv_x86_64.a
8f8135b8214cfef035b49486a863f891979efc04d97d75e2bc14cb4e28aed233 Libs/libuv.a
10 changes: 9 additions & 1 deletion Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ final class MariaDBPluginConnection: @unchecked Sendable {
private let password: String?
private let database: String
private let sslConfig: SSLConfiguration
private let enableCleartextPlugin: Bool

private let stateLock = NSLock()
private var _isConnected: Bool = false
Expand Down Expand Up @@ -176,14 +177,16 @@ final class MariaDBPluginConnection: @unchecked Sendable {
user: String,
password: String?,
database: String,
sslConfig: SSLConfiguration
sslConfig: SSLConfiguration,
enableCleartextPlugin: Bool = false
) {
self.host = host
self.port = UInt32(port)
self.user = user
self.password = password
self.database = database
self.sslConfig = sslConfig
self.enableCleartextPlugin = enableCleartextPlugin
}

deinit {
Expand Down Expand Up @@ -291,6 +294,11 @@ final class MariaDBPluginConnection: @unchecked Sendable {

mysql_options(mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4")

if enableCleartextPlugin {
var enableCleartext: my_bool = 1
mysql_options(mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN, &enableCleartext)
}

let dbToUse = database.isEmpty ? nil : database
let passToUse = password

Expand Down
53 changes: 52 additions & 1 deletion Plugins/MySQLDriverPlugin/MySQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,58 @@ final class MySQLPlugin: NSObject, TableProPlugin, DriverPlugin {
static let databaseDisplayName = "MySQL"
static let iconName = "mysql-icon"
static let defaultPort = 3306
static let additionalConnectionFields: [ConnectionField] = []
static let additionalConnectionFields: [ConnectionField] = [
ConnectionField(
id: "awsAuth",
label: String(localized: "Authentication"),
defaultValue: "off",
fieldType: .dropdown(options: [
.init(value: "off", label: String(localized: "Password")),
.init(value: "accessKey", label: String(localized: "AWS IAM (Access Key)")),
.init(value: "profile", label: String(localized: "AWS IAM (Profile)")),
.init(value: "sso", label: String(localized: "AWS IAM (SSO)"))
]),
section: .authentication,
hidesPassword: true
),
ConnectionField(
id: "awsRegion",
label: String(localized: "AWS Region"),
placeholder: "us-east-1",
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey", "profile", "sso"])
),
ConnectionField(
id: "awsAccessKeyId",
label: String(localized: "Access Key ID"),
placeholder: "AKIA...",
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey"])
),
ConnectionField(
id: "awsSecretAccessKey",
label: String(localized: "Secret Access Key"),
placeholder: "wJalr...",
fieldType: .secure,
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey"])
),
ConnectionField(
id: "awsSessionToken",
label: String(localized: "Session Token"),
placeholder: String(localized: "Optional, for temporary credentials"),
fieldType: .secure,
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey"])
),
ConnectionField(
id: "awsProfileName",
label: String(localized: "Profile Name"),
placeholder: "default",
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["profile", "sso"])
),
]
static let additionalDatabaseTypeIds: [String] = ["MariaDB"]

// MARK: - UI/Capability Metadata
Expand Down
3 changes: 2 additions & 1 deletion Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ final class MySQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
user: config.username,
password: config.password,
database: _activeDatabase,
sslConfig: sslConfig
sslConfig: sslConfig,
enableCleartextPlugin: config.additionalFields["enableCleartextPlugin"] == "true"
)

try await conn.connect()
Expand Down
50 changes: 50 additions & 0 deletions Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,56 @@ final class PostgreSQLPlugin: NSObject, TableProPlugin, DriverPlugin {
section: .authentication,
hidesPassword: true
),
ConnectionField(
id: "awsAuth",
label: String(localized: "Authentication"),
defaultValue: "off",
fieldType: .dropdown(options: [
.init(value: "off", label: String(localized: "Password")),
.init(value: "accessKey", label: String(localized: "AWS IAM (Access Key)")),
.init(value: "profile", label: String(localized: "AWS IAM (Profile)")),
.init(value: "sso", label: String(localized: "AWS IAM (SSO)"))
]),
section: .authentication,
hidesPassword: true
),
ConnectionField(
id: "awsRegion",
label: String(localized: "AWS Region"),
placeholder: "us-east-1",
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey", "profile", "sso"])
),
ConnectionField(
id: "awsAccessKeyId",
label: String(localized: "Access Key ID"),
placeholder: "AKIA...",
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey"])
),
ConnectionField(
id: "awsSecretAccessKey",
label: String(localized: "Secret Access Key"),
placeholder: "wJalr...",
fieldType: .secure,
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey"])
),
ConnectionField(
id: "awsSessionToken",
label: String(localized: "Session Token"),
placeholder: String(localized: "Optional, for temporary credentials"),
fieldType: .secure,
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["accessKey"])
),
ConnectionField(
id: "awsProfileName",
label: String(localized: "Profile Name"),
placeholder: "default",
section: .authentication,
visibleWhen: FieldVisibilityRule(fieldId: "awsAuth", values: ["profile", "sso"])
),
ConnectionField(
id: "connectionOptions",
label: String(localized: "Connection Options"),
Expand Down
2 changes: 2 additions & 0 deletions TablePro.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2884,6 +2884,7 @@
"-lssl.3",
"-lcrypto.3",
"-liconv",
"-lz",
);
PRODUCT_BUNDLE_IDENTIFIER = com.TablePro.MySQLDriver;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -2923,6 +2924,7 @@
"-lssl.3",
"-lcrypto.3",
"-liconv",
"-lz",
);
PRODUCT_BUNDLE_IDENTIFIER = com.TablePro.MySQLDriver;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
32 changes: 32 additions & 0 deletions TablePro/Core/Database/AWS/AWSAuthError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// AWSAuthError.swift
// TablePro
//

import Foundation

enum AWSAuthError: Error, LocalizedError, Equatable {
case missingAccessKey
case credentialsFileUnreadable
case profileIncomplete(String)
case regionUnknown(host: String)

var errorDescription: String? {
switch self {
case .missingAccessKey:
return String(localized: "Access Key ID and Secret Access Key are required for AWS IAM authentication.")
case .credentialsFileUnreadable:
return String(localized: "Cannot read ~/.aws/credentials.")
case .profileIncomplete(let profile):
return String(
format: String(localized: "Profile \"%@\" was not found or is missing keys in ~/.aws/credentials."),
profile
)
case .regionUnknown(let host):
return String(
format: String(localized: "Could not determine an AWS region for \"%@\". Set the AWS Region field."),
host
)
}
}
}
89 changes: 89 additions & 0 deletions TablePro/Core/Database/AWS/AWSCredentialResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// AWSCredentialResolver.swift
// TablePro
//

import Foundation

enum AWSCredentialResolver {
static func resolve(source: String, fields: [String: String]) async throws -> AWSCredentials {
switch source {
case "profile":
return try resolveProfile(fields: fields)
case "sso":
return try await resolveSSO(fields: fields)
default:
return try resolveAccessKey(fields: fields)
}
}

private static func resolveAccessKey(fields: [String: String]) throws -> AWSCredentials {
let accessKeyId = fields["awsAccessKeyId"] ?? ""
let secretAccessKey = fields["awsSecretAccessKey"] ?? ""
let sessionToken = fields["awsSessionToken"]

guard !accessKeyId.isEmpty, !secretAccessKey.isEmpty else {
throw AWSAuthError.missingAccessKey
}

return AWSCredentials(
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken?.isEmpty == true ? nil : sessionToken
)
}

private static func resolveProfile(fields: [String: String]) throws -> AWSCredentials {
let profileName = fields["awsProfileName"].flatMap { $0.isEmpty ? nil : $0 } ?? "default"
let credentialsPath = NSString("~/.aws/credentials").expandingTildeInPath

guard let content = try? String(contentsOfFile: credentialsPath, encoding: .utf8) else {
throw AWSAuthError.credentialsFileUnreadable
}

let sections = AWSSSO.parseIniSections(content)
guard let profile = sections[profileName] else {
throw AWSAuthError.profileIncomplete(profileName)
}

let accessKeyId = profile["aws_access_key_id"] ?? ""
let secretAccessKey = profile["aws_secret_access_key"] ?? ""
guard !accessKeyId.isEmpty, !secretAccessKey.isEmpty else {
throw AWSAuthError.profileIncomplete(profileName)
}

return AWSCredentials(
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: profile["aws_session_token"]
)
}

private static func resolveSSO(fields: [String: String]) async throws -> AWSCredentials {
let profileName = fields["awsProfileName"].flatMap { $0.isEmpty ? nil : $0 } ?? "default"
let configPath = NSString("~/.aws/config").expandingTildeInPath
let cacheDir = NSString("~/.aws/sso/cache").expandingTildeInPath

guard let configContent = try? String(contentsOfFile: configPath, encoding: .utf8) else {
throw AWSSSOError.configReadFailed
}

let settings = try AWSSSO.parseProfileSettings(configContent: configContent, profileName: profileName)
let accessToken = try AWSSSO.readAccessToken(
cacheDirectory: cacheDir,
settings: settings,
profileName: profileName
)
let credentials = try await AWSSSO.fetchRoleCredentials(
accessToken: accessToken,
settings: settings,
profileName: profileName,
session: URLSession.shared
)
return AWSCredentials(
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
sessionToken: credentials.sessionToken
)
}
}
Loading
Loading