import Foundation import Security enum Keychain { static let accessGroup = "$(AppIdentifierPrefix)com.treytartt.GiteaIssue" static let service = "com.treytartt.GiteaIssue" static func read(_ key: String) -> String? { var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: key, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnData as String: true, ] if let group = resolvedAccessGroup() { query[kSecAttrAccessGroup as String] = group } var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) guard status == errSecSuccess, let data = item as? Data else { return nil } return String(data: data, encoding: .utf8) } static func write(_ key: String, value: String) { let data = Data(value.utf8) var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: key, ] if let group = resolvedAccessGroup() { query[kSecAttrAccessGroup as String] = group } SecItemDelete(query as CFDictionary) var add = query add[kSecValueData as String] = data add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock SecItemAdd(add as CFDictionary, nil) } static func delete(_ key: String) { var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: key, ] if let group = resolvedAccessGroup() { query[kSecAttrAccessGroup as String] = group } SecItemDelete(query as CFDictionary) } private static func resolvedAccessGroup() -> String? { guard let prefix = appIdentifierPrefix() else { return nil } return "\(prefix)com.treytartt.GiteaIssue" } private static func appIdentifierPrefix() -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: "__appIdentifierPrefixProbe", kSecAttrService as String: service, kSecReturnAttributes as String: true, kSecMatchLimit as String: kSecMatchLimitOne, ] var result: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &result) if status == errSecSuccess, let attrs = result as? [String: Any], let group = attrs[kSecAttrAccessGroup as String] as? String, let dot = group.firstIndex(of: ".") { return String(group[..