Build iOS App / Build .ipa and publish source (push) Failing after 35s
Multi-repo PR inbox, review (approve/request changes/comment), merge with gating toggle, read-only diff viewer, and Gitea Actions check status with one-tap re-run. Ships unsigned via the iMac runner to the SideStore source.
51 lines
1.7 KiB
Swift
51 lines
1.7 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
/// Minimal Keychain wrapper for the API token and instance URL.
|
|
enum KeychainHelper {
|
|
private static let service = "com.bueso.giteapr"
|
|
|
|
static func set(_ value: String, key: String) {
|
|
guard let data = value.data(using: .utf8) else { return }
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key,
|
|
]
|
|
// Delete any existing item first (upsert semantics).
|
|
SecItemDelete(query as CFDictionary)
|
|
var attrs = query
|
|
attrs[kSecValueData as String] = data
|
|
attrs[kSecAttrAccessible as String] =
|
|
kSecAttrAccessibleAfterFirstUnlock
|
|
SecItemAdd(attrs as CFDictionary, nil)
|
|
}
|
|
|
|
static func get(_ key: String) -> String? {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
]
|
|
var result: AnyObject?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
guard status == errSecSuccess,
|
|
let data = result as? Data,
|
|
let string = String(data: data, encoding: .utf8) else {
|
|
return nil
|
|
}
|
|
return string
|
|
}
|
|
|
|
static func delete(_ key: String) {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key,
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
}
|
|
}
|