fix: configure Catalyst keychain access

This commit is contained in:
systemBlue 2026-06-07 15:14:41 +02:00
parent e1feeb865c
commit 577f1ae262
3 changed files with 55 additions and 16 deletions

View file

@ -437,6 +437,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Forji/Forji.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.5;
DEVELOPMENT_TEAM = RVT2M7QTD4;
@ -475,6 +476,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Forji/Forji.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.5;
DEVELOPMENT_TEAM = RVT2M7QTD4;

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</plist>

View file

@ -12,6 +12,12 @@ actor KeychainManager {
"\(server)_\(username)\(suffix)"
}
private nonisolated static func dataProtectionQuery(_ attributes: [String: Any]) -> [String: Any] {
var query = attributes
query[kSecUseDataProtectionKeychain as String] = true
return query
}
// MARK: - Password
func savePassword(_ password: String, for server: String, username: String) throws {
@ -53,13 +59,13 @@ actor KeychainManager {
nonisolated static func getTokenSync(for server: String, username: String) -> String? {
let account = "\(server)_\(username)_token"
let query: [String: Any] = [
let query = dataProtectionQuery([
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
]
])
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
@ -85,40 +91,40 @@ actor KeychainManager {
private func saveItem(_ value: String, forKey key: String) throws {
guard let data = value.data(using: .utf8) else {
throw KeychainError.unableToSave
throw KeychainError.unableToEncode
}
let deleteQuery: [String: Any] = [
let deleteQuery = Self.dataProtectionQuery([
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Self.serviceName,
kSecAttrAccount as String: key,
]
])
SecItemDelete(deleteQuery as CFDictionary)
let addQuery: [String: Any] = [
let addQuery = Self.dataProtectionQuery([
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Self.serviceName,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
]
])
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unableToSave
throw KeychainError.unableToSave(status)
}
}
private func getItem(forKey key: String) throws -> String {
let query: [String: Any] = [
let query = Self.dataProtectionQuery([
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Self.serviceName,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
]
])
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
@ -134,22 +140,43 @@ actor KeychainManager {
}
private func deleteItem(forKey key: String) throws {
let query: [String: Any] = [
let query = Self.dataProtectionQuery([
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Self.serviceName,
kSecAttrAccount as String: key,
]
])
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainError.unableToDelete
throw KeychainError.unableToDelete(status)
}
}
}
enum KeychainError: Error {
case unableToSave
enum KeychainError: LocalizedError {
case unableToEncode
case unableToSave(OSStatus)
case notFound
case unableToDelete
case unableToDelete(OSStatus)
var errorDescription: String? {
switch self {
case .unableToEncode:
return "Unable to encode keychain value."
case let .unableToSave(status):
return "Unable to save keychain item: \(Self.describe(status))."
case .notFound:
return "Keychain item not found."
case let .unableToDelete(status):
return "Unable to delete keychain item: \(Self.describe(status))."
}
}
private static func describe(_ status: OSStatus) -> String {
if let message = SecCopyErrorMessageString(status, nil) as String? {
return "\(message) (OSStatus \(status))"
}
return "OSStatus \(status)"
}
}