44 lines
1.8 KiB
Swift
44 lines
1.8 KiB
Swift
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 }
|
|
}
|