76 lines
2.7 KiB
Swift
76 lines
2.7 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
struct ProxyCredentials: Codable {
|
|
let url: String
|
|
let email: String
|
|
let password: String
|
|
}
|
|
|
|
struct UniFiCredentials: Codable {
|
|
let url: String
|
|
let apiKey: String
|
|
let allowUntrusted: Bool
|
|
}
|
|
|
|
enum CredentialStore {
|
|
static let proxyAccount = "nginx-proxy-manager"
|
|
static let unifiAccount = "unifi-network"
|
|
|
|
// Use the macOS login keychain so local, ad hoc signed builds also work
|
|
// without a development team's application-identifier entitlement.
|
|
private static func query(account: String) -> [String: Any] {
|
|
[
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: "io.github.nihaiden.ProxyStudio.credentials",
|
|
kSecAttrAccount as String: account,
|
|
kSecAttrSynchronizable as String: false
|
|
]
|
|
}
|
|
|
|
static func save<T: Encodable>(_ credentials: T, account: String) throws {
|
|
let data = try JSONEncoder().encode(credentials)
|
|
let query = query(account: account)
|
|
let attributes = [kSecValueData as String: data]
|
|
let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
if status == errSecItemNotFound {
|
|
var item = query
|
|
item[kSecValueData as String] = data
|
|
item[kSecAttrLabel as String] = "Proxy Studio — " + account
|
|
try check(SecItemAdd(item as CFDictionary, nil))
|
|
} else {
|
|
try check(status)
|
|
}
|
|
}
|
|
|
|
static func load<T: Decodable>(_ type: T.Type, account: String) throws -> T? {
|
|
var query = query(account: account)
|
|
query[kSecReturnData as String] = true
|
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
var result: CFTypeRef?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
if status == errSecItemNotFound { return nil }
|
|
try check(status)
|
|
guard let data = result as? Data else {
|
|
throw ServiceError.message("The saved credentials could not be read. Please connect again.")
|
|
}
|
|
do {
|
|
return try JSONDecoder().decode(type, from: data)
|
|
} catch {
|
|
throw ServiceError.message("The saved credentials could not be decoded. Please connect again.")
|
|
}
|
|
}
|
|
|
|
static func delete(account: String) throws {
|
|
let status = SecItemDelete(query(account: account) as CFDictionary)
|
|
if status != errSecItemNotFound { try check(status) }
|
|
}
|
|
|
|
private static func check(_ status: OSStatus) throws {
|
|
guard status == errSecSuccess else {
|
|
let message = SecCopyErrorMessageString(status, nil) as String? ?? "Error \(status)"
|
|
throw ServiceError.message("Keychain: \(message)")
|
|
}
|
|
}
|
|
}
|