-
Notifications
You must be signed in to change notification settings - Fork 3
pr #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pr #14
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,35 +41,14 @@ internal enum SecureStorage { | |
| throw KeychainError.conversionFailed | ||
| } | ||
|
|
||
| // Create an Access object with no trusted-application list. | ||
| // | ||
| // When a keychain item is stored WITHOUT an explicit SecAccess, macOS | ||
| // automatically creates an ACL tied to the storing app's code signature. | ||
| // For an unsigned (or ad-hoc-signed) app this means every new build gets | ||
| // a different signature, and macOS prompts the user for their system | ||
| // password on every launch because it sees a "different" app trying to | ||
| // read the item. | ||
| // | ||
| // Passing a SecAccess with an empty trusted-application array and the | ||
| // kSecACLAuthorizationAny flag tells the keychain to allow any | ||
| // application to read this item without prompting. This is the correct | ||
| // approach for unsigned, non-sandboxed apps that distribute outside the | ||
| // Mac App Store. | ||
| var access: SecAccess? | ||
| let accessStatus = SecAccessCreate("openwispher_api_keys" as CFString, [] as CFArray, &access) | ||
| guard accessStatus == errSecSuccess, let secAccess = access else { | ||
| throw KeychainError.invalidStatus(accessStatus) | ||
| } | ||
|
|
||
| let query: [String: Any] = [ | ||
| kSecClass as String: kSecClassGenericPassword, | ||
| kSecAttrAccount as String: key, | ||
| kSecAttrService as String: "openwispher_api_keys", | ||
| kSecValueData as String: data, | ||
| // AfterFirstUnlock: accessible after the user logs in once per boot, | ||
| // without prompting for the system password on every app launch. | ||
| kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, | ||
| kSecAttrAccess as String: secAccess | ||
| kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | ||
| ] | ||
|
|
||
| let status = SecItemAdd(query as CFDictionary, nil) | ||
|
|
@@ -144,32 +123,15 @@ internal enum SecureStorage { | |
| } | ||
|
|
||
|
|
||
| /// Re-write any existing keychain items to use the AfterFirstUnlock accessibility level. | ||
| /// Run once on launch to silently upgrade keys stored under the old WhenUnlocked attribute, | ||
| /// which caused a system-password prompt on every fresh app launch. | ||
| /// Legacy migration entry point kept for compatibility. | ||
| /// | ||
| /// Earlier versions attempted to migrate all providers on startup, which could trigger | ||
| /// repeated system-password prompts for each key. We now avoid any keychain I/O here and | ||
| /// only mark migration as completed. | ||
| internal static func migrateKeychainAccessibility() { | ||
| let migrationKey = "com.openwispher.keychainAccessibilityMigrated.v1" | ||
| guard !UserDefaults.standard.bool(forKey: migrationKey) else { return } | ||
|
|
||
| let providers: [TranscriptionProviderType] = [.groq, .elevenLabs, .deepgram, .sarvam] | ||
| var allSucceeded = true | ||
| for provider in providers { | ||
| // Read the existing value (if any) — this may still prompt once | ||
| // during this single migration run, but never again afterwards. | ||
| guard let existingKey = retrieveAPIKey(for: provider), !existingKey.isEmpty else { continue } | ||
| // Re-store with the new accessibility attribute (delete-then-add inside storeAPIKey) | ||
| do { | ||
| try storeAPIKey(existingKey, for: provider) | ||
| } catch { | ||
| print("⚠️ SecureStorage: failed to migrate keychain accessibility for \(provider.rawValue): \(error)") | ||
| allSucceeded = false | ||
| } | ||
| } | ||
|
|
||
| // Only mark migration complete if every present key was successfully re-written. | ||
| if allSucceeded { | ||
| UserDefaults.standard.set(true, forKey: migrationKey) | ||
| } | ||
| UserDefaults.standard.set(true, forKey: migrationKey) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| /// Migrate existing UserDefaults keys to Keychain (one-time migration) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
SecItemAddquery no longer includeskSecAttrAccess, so macOS falls back to a default ACL bound to the app’s current code signature; in this repo’s unsigned distribution context (dhavnii/UpdateManager.swift:5), that can cause existing saved keys to trigger password prompts again after rebuilds/updates because the signer identity changes. The removedSecAccessCreate(..., [] ...)path was the compatibility mechanism for that case, so this change reintroduces a launch-time keychain access regression for users who persist API keys.Useful? React with 👍 / 👎.