import Foundation import Security protocol TokenStore { func token(origin: String) throws -> String? func save(_ token: String?, origin: String) throws } struct KeychainTokenStore: TokenStore { private func query(_ origin: String) -> [String: Any] { [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "dev.shane.voice.auth", kSecAttrAccount as String: origin] } func token(origin: String) throws -> String? { var q = query(origin) q[kSecReturnData as String] = true q[kSecMatchLimit as String] = kSecMatchLimitOne var result: CFTypeRef? let status = SecItemCopyMatching(q as CFDictionary, &result) if status == errSecItemNotFound { return nil } guard status == errSecSuccess, let data = result as? Data else { throw StoreError.failed } return String(data: data, encoding: .utf8) } func save(_ token: String?, origin: String) throws { let q = query(origin) let deletion = SecItemDelete(q as CFDictionary) guard deletion == errSecSuccess || deletion == errSecItemNotFound else { throw StoreError.failed } guard let token else { return } var item = q item[kSecValueData as String] = Data(token.utf8) item[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly guard SecItemAdd(item as CFDictionary, nil) == errSecSuccess else { throw StoreError.failed } } enum StoreError: LocalizedError { case failed var errorDescription: String? { "Could not securely save the sign-in on this iPhone." } } } final class MemoryTokenStore: TokenStore { private var values: [String: String] = [:] func token(origin: String) throws -> String? { values[origin] } func save(_ token: String?, origin: String) throws { values[origin] = token } }