60 lines
1.9 KiB
Swift
60 lines
1.9 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
struct StoredCredentials: Codable {
|
|
let serverURL: String
|
|
let username: String
|
|
let token: String
|
|
}
|
|
|
|
enum KeychainError: Error {
|
|
case osStatus(OSStatus)
|
|
case encodingFailed
|
|
}
|
|
|
|
enum KeychainStore {
|
|
private static let service = "com.local.Audiobookshelf-swift.auth"
|
|
private static let account = "primary"
|
|
|
|
static func save(_ creds: StoredCredentials) throws {
|
|
let data = try JSONEncoder().encode(creds)
|
|
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
|
|
var attributes = query
|
|
attributes[kSecValueData as String] = data
|
|
attributes[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
|
|
let status = SecItemAdd(attributes as CFDictionary, nil)
|
|
guard status == errSecSuccess else { throw KeychainError.osStatus(status) }
|
|
}
|
|
|
|
static func load() -> StoredCredentials? {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
]
|
|
var item: CFTypeRef?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
guard status == errSecSuccess, let data = item as? Data else { return nil }
|
|
return try? JSONDecoder().decode(StoredCredentials.self, from: data)
|
|
}
|
|
|
|
static func delete() {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
}
|
|
}
|