Files
ABS-Client/ABS Client/Audiobookshelf swift/Services/KeychainStore.swift
2026-05-17 21:06:59 +02:00

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)
}
}