You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
LeStorage/LeStorage/Utils/KeychainStore.swift

86 lines
3.3 KiB

//
// KeychainStore.swift
// LeCountdown
//
// Created by Laurent Morvillier on 20/12/2023.
//
import Foundation
enum KeychainError: Error {
case keychainItemNotFound(serverId: String)
case unexpectedPasswordData
case unhandledError(status: OSStatus)
var errorDescription: String? {
switch self {
case .keychainItemNotFound(let serverId):
return "The keychainItem was not found: \(serverId)"
case .unexpectedPasswordData:
return "Keychain error: The data could not be converted to string"
case .unhandledError(let status):
return "Keychain error: Unmanaged status: \(status)"
}
}
}
class KeychainStore {
let serverId: String
init(serverId: String) {
self.serverId = serverId
}
func add(username: String, value: String) throws {
let valueData = value.data(using: .utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: username,
kSecAttrServer as String: self.serverId,
kSecValueData as String: valueData]
let status: OSStatus = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
}
func add(value: String) throws {
let valueData = value.data(using: .utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: self.serverId,
kSecValueData as String: valueData]
let status: OSStatus = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
}
func getValue() throws -> String {
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: self.serverId,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
var item: CFTypeRef?
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { throw KeychainError.keychainItemNotFound(serverId: self.serverId) }
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
guard let existingItem = item as? [String : Any],
let tokenData = existingItem[kSecValueData as String] as? Data,
let token = String(data: tokenData, encoding: .utf8) else {
throw KeychainError.unexpectedPasswordData
}
return token
}
func deleteValue() throws {
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: self.serverId]
let status: OSStatus = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
}
}