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.
86 lines
3.3 KiB
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) }
|
|
}
|
|
|
|
}
|
|
|