05/02 Phase B
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
// 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) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user