import Foundation import Security enum NativeCredentials { private static var service: String { (Bundle.main.bundleIdentifier ?? "oficina.native") + ".session" } private static func query(_ origin: String) -> [String: Any] { [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: origin] } static func read(_ origin: String) -> NativeSession? { var q = query(origin) q[kSecReturnData as String] = true q[kSecMatchLimit as String] = kSecMatchLimitOne var result: CFTypeRef? guard SecItemCopyMatching(q as CFDictionary, &result) == errSecSuccess, let data = result as? Data else { return nil } return try? JSONDecoder().decode(NativeSession.self, from: data) } static func save(_ session: NativeSession, origin: String) throws { let data = try JSONEncoder().encode(session) let updated = SecItemUpdate(query(origin) as CFDictionary, [kSecValueData as String: data] as CFDictionary) if updated == errSecSuccess { return } guard updated == errSecItemNotFound else { throw NativeClientError.message("Could not update this session in Keychain.") } var q = query(origin) q[kSecValueData as String] = data q[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly guard SecItemAdd(q as CFDictionary, nil) == errSecSuccess else { throw NativeClientError.message("Could not save this session in Keychain.") } } static func remove(_ origin: String) { SecItemDelete(query(origin) as CFDictionary) } }