48 lines
1.6 KiB
Swift
48 lines
1.6 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
/// Secure persistence for the session token using the iOS Keychain.
|
|
/// Never stores the session in UserDefaults or plain files.
|
|
struct KeychainStore {
|
|
|
|
private let service = "com.proximity.app.session"
|
|
|
|
func save(_ session: Session) {
|
|
let data = (try? JSONEncoder().encode(session)) ?? Data()
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: "session"
|
|
]
|
|
// Delete existing, then add.
|
|
SecItemDelete(query as CFDictionary)
|
|
var add = query
|
|
add[kSecValueData as String] = data
|
|
SecItemAdd(add as CFDictionary, nil)
|
|
}
|
|
|
|
func loadSession() -> Session? {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: "session",
|
|
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 else { return nil }
|
|
return try? JSONDecoder().decode(Session.self, from: data)
|
|
}
|
|
|
|
func clearSession() {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: "session"
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
}
|
|
}
|