Initial Commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user