import Foundation import Security // MARK: - Managed slskd API Credentials /// Auto-generated credentials for the managed slskd instance. /// Separate Keychain service from SlskdCredentials (which stores Soulseek P2P login). enum ManagedSlskdKeychainService { private static let service = "com.mixboard.slskd-managed" static func save(account: String, value: String) throws { guard let data = value.data(using: .utf8) else { throw KeychainService.KeychainError.saveFailed(errSecParam) } let deleteQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, ] SecItemDelete(deleteQuery as CFDictionary) let addQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked, ] let status = SecItemAdd(addQuery as CFDictionary, nil) guard status == errSecSuccess else { throw KeychainService.KeychainError.saveFailed(status) } } static func load(account: String) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, kSecAttrAccount as String: account, ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } } /// Manages auto-generated API credentials for the managed slskd subprocess. /// On first access, generates random credentials and persists them in Keychain. @MainActor final class ManagedSlskdCredentials { static let shared = ManagedSlskdCredentials() /// Returns existing credentials or generates new ones. func ensureCredentials() throws -> (username: String, password: String) { if let u = ManagedSlskdKeychainService.load(account: "username"), let p = ManagedSlskdKeychainService.load(account: "password"), !u.isEmpty, !p.isEmpty { return (u, p) } let username = "mixboard" let password = UUID().uuidString try ManagedSlskdKeychainService.save(account: "username", value: username) try ManagedSlskdKeychainService.save(account: "password", value: password) return (username, password) } var username: String? { ManagedSlskdKeychainService.load(account: "username") } var password: String? { ManagedSlskdKeychainService.load(account: "password") } }