05/02 Phase C 2

This commit is contained in:
Nguyen Ngo
2026-05-03 09:29:12 -04:00
parent 59e698b5f7
commit 51d2923e6d
6 changed files with 439 additions and 309 deletions
+12 -11
View File
@@ -3,8 +3,10 @@
// 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.
// All methods are marked nonisolated so they can be called from any
// actor context (including APIClient which is an actor) without
// Swift 6 actor-isolation warnings.
// Security framework calls are internally thread-safe.
import Foundation
import Security
@@ -15,10 +17,9 @@ enum KeychainHelper {
// Write
@discardableResult
static func set(_ value: String, forKey key: String) -> Bool {
nonisolated 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,
@@ -26,10 +27,10 @@ enum KeychainHelper {
SecItemDelete(deleteQuery as CFDictionary)
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
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
@@ -37,7 +38,7 @@ enum KeychainHelper {
// Read
static func get(_ key: String) -> String? {
nonisolated static func get(_ key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
@@ -56,7 +57,7 @@ enum KeychainHelper {
// Delete
@discardableResult
static func delete(_ key: String) -> Bool {
nonisolated static func delete(_ key: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
@@ -67,7 +68,7 @@ enum KeychainHelper {
// Delete All JQC Keys
static func clearAll() {
nonisolated static func clearAll() {
let keys = [
Constants.Keychain.accessToken,
Constants.Keychain.refreshToken,