Improve iOS quick passkey autofill to work on iOS 18+

This commit is contained in:
Leendert de Borst
2025-11-02 20:41:13 +01:00
parent 7f01e2a9a0
commit 03d8e15eeb
3 changed files with 52 additions and 51 deletions

View File

@@ -228,52 +228,58 @@ public class CredentialProviderViewController: ASCredentialProviderViewControlle
// If we're in quick return mode, now trigger the unlock and complete the request
// The loading view is already visible from viewWillAppear
if isQuickReturnMode {
let vaultStore = VaultStore()
// Dispatch async to ensure the view is fully rendered before showing biometric prompt
// This prevents a race condition where the first tap doesn't trigger the biometric UI
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if !sanityChecks(vaultStore: vaultStore) {
return
}
let vaultStore = VaultStore()
// Check if biometric authentication is available
if !vaultStore.isBiometricAuthEnabled() {
print("Quick return failed: Biometric auth not enabled")
self.extensionContext.cancelRequest(withError: NSError(
domain: ASExtensionErrorDomain,
code: ASExtensionError.failed.rawValue,
userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("biometric_auth_required_message", comment: "Please enable Face ID in the main AliasVault app to use autofill.")]
))
return
}
do {
try vaultStore.unlockVault()
if let passkeyRequest = quickReturnPasskeyRequest {
handleQuickReturnPasskeyCredential(vaultStore: vaultStore, request: passkeyRequest)
} else if let passwordRequest = quickReturnPasswordRequest {
handleQuickReturnPasswordCredential(vaultStore: vaultStore, request: passwordRequest)
if !self.sanityChecks(vaultStore: vaultStore) {
return
}
} catch let error as NSError {
print("Quick return vault unlock failed: \(error)")
// Provide specific error message based on error code
var errorMessage = error.localizedDescription
if error.domain == "VaultStore" {
switch error.code {
case 3:
errorMessage = NSLocalizedString("no_encryption_key_message", comment: "No encryption key found. Please unlock the vault in the main AliasVault app first.")
case 9:
errorMessage = NSLocalizedString("keychain_error_message", comment: "Failed to retrieve encryption key. This may be due to cancelled biometric authentication.")
default:
break
// Check if biometric authentication is available
if !vaultStore.isBiometricAuthEnabled() {
print("Quick return failed: Biometric auth not enabled")
self.extensionContext.cancelRequest(withError: NSError(
domain: ASExtensionErrorDomain,
code: ASExtensionError.failed.rawValue,
userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("biometric_auth_required_message", comment: "Please enable Face ID in the main AliasVault app to use autofill.")]
))
return
}
do {
try vaultStore.unlockVault()
if let passkeyRequest = self.quickReturnPasskeyRequest {
self.handleQuickReturnPasskeyCredential(vaultStore: vaultStore, request: passkeyRequest)
} else if let passwordRequest = self.quickReturnPasswordRequest {
self.handleQuickReturnPasswordCredential(vaultStore: vaultStore, request: passwordRequest)
}
}
} catch let error as NSError {
print("Quick return vault unlock failed: \(error)")
self.extensionContext.cancelRequest(withError: NSError(
domain: ASExtensionErrorDomain,
code: ASExtensionError.failed.rawValue,
userInfo: [NSLocalizedDescriptionKey: errorMessage]
))
// Provide specific error message based on error code
var errorMessage = error.localizedDescription
if error.domain == "VaultStore" {
switch error.code {
case 3:
errorMessage = NSLocalizedString("no_encryption_key_message", comment: "No encryption key found. Please unlock the vault in the main AliasVault app first.")
case 9:
errorMessage = NSLocalizedString("keychain_error_message", comment: "Failed to retrieve encryption key. This may be due to cancelled biometric authentication.")
default:
break
}
}
self.extensionContext.cancelRequest(withError: NSError(
domain: ASExtensionErrorDomain,
code: ASExtensionError.failed.rawValue,
userInfo: [NSLocalizedDescriptionKey: errorMessage]
))
}
}
}
}

View File

@@ -452,17 +452,8 @@ public class VaultManager: NSObject {
// Get all credentials from the vault
let credentials = try vaultStore.getAllCredentials()
if #available(iOS 26.0, *) {
// iOS 26+: Register both passwords and passkeys for QuickType and manual selection
try await CredentialIdentityStore.shared.saveCredentialIdentities(credentials)
} else {
// iOS 17 and 18: Only register passkeys (skip passwords for QuickType as biometric unlock is buggy on these versions)
let passkeyOnlyCredentials = credentials.filter { credential in
guard let passkeys = credential.passkeys else { return false }
return !passkeys.isEmpty
}
try await CredentialIdentityStore.shared.saveCredentialIdentities(passkeyOnlyCredentials)
}
// Register both passwords and passkeys for QuickType and manual selection
try await CredentialIdentityStore.shared.saveCredentialIdentities(credentials)
await MainActor.run {
resolve(nil)

View File

@@ -222,6 +222,10 @@ extension VaultStore {
context.interactionNotAllowed = false
context.localizedReason = "Authenticate to unlock your vault"
// Add a small delay to ensure the context is fully ready
// This helps prevent race conditions where the biometric prompt doesn't show on first tap
Thread.sleep(forTimeInterval: 0.05)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: VaultConstants.keychainService,