From 7b59ee6098c4649a51cafe4b58e3a675567e2c08 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 1 Apr 2026 11:36:01 -0500 Subject: feat: add multi-account support Implements: https://todo.sr.ht/~ccleberg/Hutch/11 --- Hutch/Extensions/KeychainHelper.swift | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'Hutch/Extensions/KeychainHelper.swift') diff --git a/Hutch/Extensions/KeychainHelper.swift b/Hutch/Extensions/KeychainHelper.swift index 01f8e5a..d53c80e 100644 --- a/Hutch/Extensions/KeychainHelper.swift +++ b/Hutch/Extensions/KeychainHelper.swift @@ -5,6 +5,7 @@ enum KeychainHelper: Sendable { private static let service = "net.cleberg.Hutch" private static let tokenAccount = "srht-access-token" + private static let accountsAccount = "srht-accounts" // MARK: - Save @@ -57,6 +58,45 @@ enum KeychainHelper: Sendable { return token } + // MARK: - Multi-account list + + static func saveAccounts(_ accounts: [AccountEntry]) throws { + let data = try JSONEncoder().encode(accounts) + + let deleteQuery: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: accountsAccount + ] + SecItemDelete(deleteQuery as CFDictionary) + + let addQuery: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: accountsAccount, + kSecValueData as String: data, + kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly + ] + let status = SecItemAdd(addQuery as CFDictionary, nil) + guard status == errSecSuccess else { + throw KeychainError.saveFailed(status) + } + } + + static func loadAccounts() -> [AccountEntry] { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: accountsAccount, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne + ] + var result: AnyObject? + guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess, + let data = result as? Data else { return [] } + return (try? JSONDecoder().decode([AccountEntry].self, from: data)) ?? [] + } + // MARK: - Delete static func deleteToken() throws { -- cgit v1.2.3