row editing

This commit is contained in:
Mark Gregory
2026-10-21 09:47:53 +01:00
parent 0a4cca2b41
commit 60cbf790db
5 changed files with 35 additions and 14 deletions
+4 -4
View File
@@ -221,8 +221,8 @@
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "DBSyn";
MARKETING_VERSION = 1.0.8;
CURRENT_PROJECT_VERSION = 81;
MARKETING_VERSION = 1.1.0;
CURRENT_PROJECT_VERSION = 82;
ENABLE_TESTABILITY = YES;
ONLY_ACTIVE_ARCH = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 ABSDEV. All rights reserved.";
@@ -244,8 +244,8 @@
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "DBSyn";
MARKETING_VERSION = 1.0.8;
CURRENT_PROJECT_VERSION = 81;
MARKETING_VERSION = 1.1.0;
CURRENT_PROJECT_VERSION = 82;
ENABLE_TESTABILITY = YES;
ONLY_ACTIVE_ARCH = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 ABSDEV. All rights reserved.";
+5 -3
View File
@@ -12,15 +12,16 @@ struct ConnectionProfile: Identifiable, Codable, Hashable {
// Optional SSH transport. Existing saved profiles decode with these defaults.
var useSSHTunnel = false
var useTLS = true
var verifyTLS = true
var sshHost = ""
var sshPort = 22
var sshUser = ""
var sshIdentityFile = ""
enum CodingKeys: String, CodingKey { case id, name, host, port, username, database, useSSHTunnel, useTLS, sshHost, sshPort, sshUser, sshIdentityFile }
init(id: UUID = UUID(), name: String = "Local MySQL", host: String = "127.0.0.1", port: Int = 3306, username: String = "root", database: String = "", useSSHTunnel: Bool = false, useTLS: Bool = true, sshHost: String = "", sshPort: Int = 22, sshUser: String = "", sshIdentityFile: String = "") {
enum CodingKeys: String, CodingKey { case id, name, host, port, username, database, useSSHTunnel, useTLS, verifyTLS, sshHost, sshPort, sshUser, sshIdentityFile }
init(id: UUID = UUID(), name: String = "Local MySQL", host: String = "127.0.0.1", port: Int = 3306, username: String = "root", database: String = "", useSSHTunnel: Bool = false, useTLS: Bool = true, verifyTLS: Bool = true, sshHost: String = "", sshPort: Int = 22, sshUser: String = "", sshIdentityFile: String = "") {
self.id = id; self.name = name; self.host = host; self.port = port; self.username = username; self.database = database
self.useSSHTunnel = useSSHTunnel; self.useTLS = useTLS; self.sshHost = sshHost; self.sshPort = sshPort; self.sshUser = sshUser; self.sshIdentityFile = sshIdentityFile
self.useSSHTunnel = useSSHTunnel; self.useTLS = useTLS; self.verifyTLS = verifyTLS; self.sshHost = sshHost; self.sshPort = sshPort; self.sshUser = sshUser; self.sshIdentityFile = sshIdentityFile
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
@@ -32,6 +33,7 @@ struct ConnectionProfile: Identifiable, Codable, Hashable {
database = try c.decode(String.self, forKey: .database)
useSSHTunnel = try c.decodeIfPresent(Bool.self, forKey: .useSSHTunnel) ?? false
useTLS = try c.decodeIfPresent(Bool.self, forKey: .useTLS) ?? true
verifyTLS = try c.decodeIfPresent(Bool.self, forKey: .verifyTLS) ?? true
sshHost = try c.decodeIfPresent(String.self, forKey: .sshHost) ?? ""
sshPort = try c.decodeIfPresent(Int.self, forKey: .sshPort) ?? 22
sshUser = try c.decodeIfPresent(String.self, forKey: .sshUser) ?? ""
+6 -2
View File
@@ -2550,8 +2550,12 @@ struct ConnectionEditor: View {
TextField("Default Schema", text: $profile.database)
}
Section("Secure Transport") {
Toggle("Require verified TLS for direct remote connections", isOn: $profile.useTLS)
Toggle("Verify server identity (recommended)", isOn: $profile.verifyTLS)
.disabled(profile.useSSHTunnel)
if !profile.verifyTLS && !profile.useSSHTunnel {
Text("Encrypted TLS (MySQL REQUIRED): traffic is encrypted, but the server identity is NOT verified. Only use with servers you trust.")
.font(.caption).foregroundStyle(.orange)
}
Toggle("Connect through SSH tunnel", isOn: $profile.useSSHTunnel)
if profile.useSSHTunnel {
TextField("SSH Host", text: $profile.sshHost)
@@ -2561,7 +2565,7 @@ struct ConnectionEditor: View {
Text("Uses your system SSH keys and known_hosts. MySQL hostname is resolved from the SSH server.")
.font(.caption).foregroundStyle(.secondary)
} else {
Text("Direct remote MySQL uses certificate-verified TLS. The server certificate must match its hostname and be trusted by macOS.")
Text(profile.verifyTLS ? "Direct remote MySQL uses verified TLS." : "Direct remote MySQL uses encrypted TLS without server identity verification.")
.font(.caption).foregroundStyle(.secondary)
}
}
+8 -5
View File
@@ -205,11 +205,13 @@ final class MySQLWireConnection {
private let maxPacket = 16_777_215
private let serverHost: String
private let requireTLS: Bool
private let verifyTLS: Bool
private var tlsActive = false
init(host: String, port: Int, requireTLS: Bool = false) throws {
init(host: String, port: Int, requireTLS: Bool = false, verifyTLS: Bool = true) throws {
self.serverHost = host
self.requireTLS = requireTLS
self.verifyTLS = verifyTLS
guard (1...65535).contains(port) else { throw MySQLWireError(message: "Invalid port") }
var read: Unmanaged<CFReadStream>?
var write: Unmanaged<CFWriteStream>?
@@ -271,8 +273,8 @@ final class MySQLWireConnection {
private func upgradeToTLS() throws {
let settings: [String: Any] = [
kCFStreamSSLLevel as String: kCFStreamSocketSecurityLevelNegotiatedSSL,
kCFStreamSSLValidatesCertificateChain as String: true,
kCFStreamSSLPeerName as String: serverHost
kCFStreamSSLValidatesCertificateChain as String: verifyTLS,
kCFStreamSSLPeerName as String: verifyTLS ? serverHost : kCFNull as Any
]
let key = Stream.PropertyKey(rawValue: kCFStreamPropertySSLSettings as String)
guard input.setProperty(settings, forKey: key),
@@ -509,8 +511,9 @@ private final class PersistentMySQLSessions {
let endpoint = try DBSynSSHTunnels.shared.endpoint(for: profile)
let connection = try MySQLWireConnection(
host: endpoint.0, port: endpoint.1,
requireTLS: !profile.useSSHTunnel && profile.useTLS &&
!["127.0.0.1", "localhost", "::1"].contains(endpoint.0.lowercased())
requireTLS: !profile.useSSHTunnel &&
!["127.0.0.1", "localhost", "::1"].contains(endpoint.0.lowercased()),
verifyTLS: profile.verifyTLS
)
connection.markHost(endpoint.0)
try connection.authenticate(user: profile.username,
+12
View File
@@ -0,0 +1,12 @@
# DBSyn 1.1.0 (Build 82)
## Connection security
- Added a per-connection **Verify server identity** option. Enabled by default, including for existing profiles.
- When disabled, direct remote connections still negotiate encrypted MySQL TLS, equivalent to `--ssl-mode=REQUIRED`, without validating the server identity.
- SSH tunnel behaviour is unchanged.
- The connection form warns when identity verification is disabled.
**Security:** Unverified TLS protects against passive interception but not an active impersonation attack. Use verified TLS whenever possible.
## Validation
Source changes inspected; macOS/Xcode build and live database connection testing must be performed on a Mac.