import Foundation import Security enum KeychainService { private static let serviceName = "com.aiusage.app" static func save(_ value: String, account: String) throws { let data = Data(value.utf8) let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: serviceName, kSecAttrAccount as String: account, ] SecItemDelete(query as CFDictionary) var addQuery = query addQuery[kSecValueData as String] = data addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock let status = SecItemAdd(addQuery as CFDictionary, nil) guard status == errSecSuccess else { throw KeychainError.unhandled(status) } } static func load(account: String) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: serviceName, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data, let string = String(data: data, encoding: .utf8) else { return nil } return string } static func delete(account: String) { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: serviceName, kSecAttrAccount as String: account, ] SecItemDelete(query as CFDictionary) } static func keychainAccount(provider: ProviderID, authMode: AuthMode) -> String { "\(provider.rawValue).\(authMode.rawValue)" } } enum KeychainError: Error, LocalizedError { case unhandled(OSStatus) var errorDescription: String? { switch self { case .unhandled(let status): "Keychain error (\(status))" } } }