// Auth/KeychainHelper.swift // ------------------------- // Thin wrapper around iOS Security framework for storing sensitive data // (tokens) in the device Keychain. // // The Keychain persists across app reinstalls (on the same device) and // is encrypted by the OS. Never store tokens in UserDefaults. import Foundation import Security import Combine enum KeychainHelper { // ── Write ───────────────────────────────────────────────────────────── @discardableResult static func set(_ value: String, forKey key: String) -> Bool { guard let data = value.data(using: .utf8) else { return false } // Delete any existing entry first to avoid duplicate-item errors let deleteQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, ] SecItemDelete(deleteQuery as CFDictionary) let addQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, ] let status = SecItemAdd(addQuery as CFDictionary, nil) return status == errSecSuccess } // ── Read ────────────────────────────────────────────────────────────── static func get(_ key: String) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, 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 } // ── Delete ──────────────────────────────────────────────────────────── @discardableResult static func delete(_ key: String) -> Bool { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, ] let status = SecItemDelete(query as CFDictionary) return status == errSecSuccess || status == errSecItemNotFound } // ── Delete All JQC Keys ─────────────────────────────────────────────── static func clearAll() { let keys = [ Constants.Keychain.accessToken, Constants.Keychain.refreshToken, Constants.Keychain.userId, Constants.Keychain.userRole, Constants.Keychain.username, Constants.Keychain.displayName, ] keys.forEach { delete($0) } } }