Connect macOS app to backend device bootstrap
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
public protocol KeychainService: Sendable {
|
||||
func save(_ value: String, forKey key: String) async
|
||||
func loadValue(forKey key: String) async -> String?
|
||||
func save(_ value: String, forKey key: String) async throws
|
||||
func loadValue(forKey key: String) async throws -> String?
|
||||
}
|
||||
|
||||
public actor MemoryKeychainService: KeychainService {
|
||||
@@ -10,11 +11,87 @@ public actor MemoryKeychainService: KeychainService {
|
||||
|
||||
public init() {}
|
||||
|
||||
public func save(_ value: String, forKey key: String) async {
|
||||
public func save(_ value: String, forKey key: String) async throws {
|
||||
storage[key] = value
|
||||
}
|
||||
|
||||
public func loadValue(forKey key: String) async -> String? {
|
||||
public func loadValue(forKey key: String) async throws -> String? {
|
||||
storage[key]
|
||||
}
|
||||
}
|
||||
|
||||
public enum KeychainServiceError: LocalizedError, Sendable {
|
||||
case invalidValue
|
||||
case unexpectedStatus(OSStatus)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidValue:
|
||||
return "The keychain returned invalid data."
|
||||
case let .unexpectedStatus(status):
|
||||
if let message = SecCopyErrorMessageString(status, nil) as String? {
|
||||
return message
|
||||
}
|
||||
|
||||
return "The keychain returned status \(status)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public actor SystemKeychainService: KeychainService {
|
||||
private let service: String
|
||||
|
||||
public init(service: String = "de.diyaa.velody") {
|
||||
self.service = service
|
||||
}
|
||||
|
||||
public func save(_ value: String, forKey key: String) async throws {
|
||||
let query = baseQuery(forKey: key)
|
||||
let deleteStatus = SecItemDelete(query as CFDictionary)
|
||||
|
||||
guard deleteStatus == errSecSuccess || deleteStatus == errSecItemNotFound else {
|
||||
throw KeychainServiceError.unexpectedStatus(deleteStatus)
|
||||
}
|
||||
|
||||
var addQuery = query
|
||||
addQuery[kSecValueData as String] = Data(value.utf8)
|
||||
|
||||
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
||||
guard addStatus == errSecSuccess else {
|
||||
throw KeychainServiceError.unexpectedStatus(addStatus)
|
||||
}
|
||||
}
|
||||
|
||||
public func loadValue(forKey key: String) async throws -> String? {
|
||||
var query = baseQuery(forKey: key)
|
||||
query[kSecReturnData as String] = kCFBooleanTrue
|
||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
|
||||
var result: AnyObject?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
|
||||
switch status {
|
||||
case errSecSuccess:
|
||||
guard
|
||||
let data = result as? Data,
|
||||
let value = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
throw KeychainServiceError.invalidValue
|
||||
}
|
||||
|
||||
return value
|
||||
case errSecItemNotFound:
|
||||
return nil
|
||||
default:
|
||||
throw KeychainServiceError.unexpectedStatus(status)
|
||||
}
|
||||
}
|
||||
|
||||
private func baseQuery(forKey key: String) -> [String: Any] {
|
||||
[
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: key,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user