77
88import Foundation
99import os
10+ import TableProPluginKit
1011
1112/// Service for persisting database connections
1213final class ConnectionStorage {
@@ -101,6 +102,9 @@ final class ConnectionStorage {
101102 deleteSSHPassword ( for: connection. id)
102103 deleteKeyPassphrase ( for: connection. id)
103104 deleteTOTPSecret ( for: connection. id)
105+
106+ let secureFieldIds = Self . secureFieldIds ( for: connection. type)
107+ deleteAllPluginSecureFields ( for: connection. id, fieldIds: secureFieldIds)
104108 }
105109
106110 /// Duplicate a connection with a new UUID and "(Copy)" suffix
@@ -150,6 +154,13 @@ final class ConnectionStorage {
150154 saveTOTPSecret ( totpSecret, for: newId)
151155 }
152156
157+ let secureFieldIds = Self . secureFieldIds ( for: connection. type)
158+ for fieldId in secureFieldIds {
159+ if let value = loadPluginSecureField ( fieldId: fieldId, for: connection. id) {
160+ savePluginSecureField ( value, fieldId: fieldId, for: newId)
161+ }
162+ }
163+
153164 return duplicate
154165 }
155166
@@ -211,6 +222,29 @@ final class ConnectionStorage {
211222 KeychainHelper . shared. delete ( key: key)
212223 }
213224
225+ // MARK: - Plugin Secure Field Storage
226+
227+ func savePluginSecureField( _ value: String , fieldId: String , for connectionId: UUID ) {
228+ let key = " com.TablePro.plugin. \( fieldId) . \( connectionId. uuidString) "
229+ KeychainHelper . shared. saveString ( value, forKey: key)
230+ }
231+
232+ func loadPluginSecureField( fieldId: String , for connectionId: UUID ) -> String ? {
233+ let key = " com.TablePro.plugin. \( fieldId) . \( connectionId. uuidString) "
234+ return KeychainHelper . shared. loadString ( forKey: key)
235+ }
236+
237+ func deletePluginSecureField( fieldId: String , for connectionId: UUID ) {
238+ let key = " com.TablePro.plugin. \( fieldId) . \( connectionId. uuidString) "
239+ KeychainHelper . shared. delete ( key: key)
240+ }
241+
242+ func deleteAllPluginSecureFields( for connectionId: UUID , fieldIds: [ String ] ) {
243+ for fieldId in fieldIds {
244+ deletePluginSecureField ( fieldId: fieldId, for: connectionId)
245+ }
246+ }
247+
214248 // MARK: - TOTP Secret Storage
215249
216250 func saveTOTPSecret( _ secret: String , for connectionId: UUID ) {
@@ -227,6 +261,41 @@ final class ConnectionStorage {
227261 let key = " com.TablePro.totpsecret. \( connectionId. uuidString) "
228262 KeychainHelper . shared. delete ( key: key)
229263 }
264+
265+ // MARK: - Plugin Secure Field Migration
266+
267+ private static func secureFieldIds( for databaseType: DatabaseType ) -> [ String ] {
268+ ( PluginMetadataRegistry . shared. snapshot ( forTypeId: databaseType. pluginTypeId) ?
269+ . connection. additionalConnectionFields ?? [ ] )
270+ . filter ( \. isSecure) . map ( \. id)
271+ }
272+
273+ func migratePluginSecureFieldsIfNeeded( ) {
274+ let migrationKey = " com.TablePro.pluginSecureFieldsMigrated "
275+ guard !UserDefaults. standard. bool ( forKey: migrationKey) else { return }
276+ defer { UserDefaults . standard. set ( true , forKey: migrationKey) }
277+
278+ var connections = loadConnections ( )
279+ var changed = false
280+
281+ for index in connections. indices {
282+ let secureFields = ( PluginMetadataRegistry . shared
283+ . snapshot ( forTypeId: connections [ index] . type. pluginTypeId) ?
284+ . connection. additionalConnectionFields ?? [ ] )
285+ . filter ( \. isSecure)
286+ for field in secureFields {
287+ if let value = connections [ index] . additionalFields [ field. id] , !value. isEmpty {
288+ savePluginSecureField ( value, fieldId: field. id, for: connections [ index] . id)
289+ connections [ index] . additionalFields. removeValue ( forKey: field. id)
290+ changed = true
291+ }
292+ }
293+ }
294+
295+ if changed {
296+ saveConnections ( connections)
297+ }
298+ }
230299}
231300
232301// MARK: - Stored Connection (Codable wrapper)
0 commit comments