Update iOS unlock flow to support both biometrics and pin fallback (#1562)
This commit is contained in:
committed by
Leendert de Borst
parent
749980f58f
commit
b9791a0563
@@ -142,46 +142,64 @@ public class CredentialProviderViewController: ASCredentialProviderViewControlle
|
||||
return
|
||||
}
|
||||
|
||||
// If PIN is enabled, show PIN unlock immediately (PIN takes priority)
|
||||
// Priority: Biometric -> PIN -> Error
|
||||
// Try biometric first if enabled
|
||||
if biometricEnabled {
|
||||
do {
|
||||
// Try to unlock the vault with biometric
|
||||
try vaultStore.unlockVault()
|
||||
|
||||
// Unlock succeeded - process the credential request
|
||||
if let passkeyRequest = self.quickReturnPasskeyRequest {
|
||||
self.handleQuickReturnPasskeyCredential(vaultStore: vaultStore, request: passkeyRequest)
|
||||
} else if let passwordRequest = self.quickReturnPasswordRequest {
|
||||
self.handleQuickReturnPasswordCredential(vaultStore: vaultStore, request: passwordRequest)
|
||||
}
|
||||
return
|
||||
} catch let error as NSError {
|
||||
print("Quick return biometric unlock failed: \(error)")
|
||||
|
||||
// Biometric failed - try PIN fallback if available
|
||||
if pinEnabled {
|
||||
print("Falling back to PIN unlock")
|
||||
self.showQuickReturnPinUnlock(vaultStore: vaultStore)
|
||||
return
|
||||
}
|
||||
|
||||
// No PIN fallback - report the error
|
||||
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 8, 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]
|
||||
))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Biometric not enabled - try PIN directly
|
||||
if pinEnabled {
|
||||
self.showQuickReturnPinUnlock(vaultStore: vaultStore)
|
||||
return
|
||||
}
|
||||
|
||||
// Only biometric is enabled - try to unlock with biometric
|
||||
// No loading view shown - Face ID will appear immediately on clean background
|
||||
do {
|
||||
// Try to unlock the vault with biometric
|
||||
try vaultStore.unlockVault()
|
||||
|
||||
// Unlock succeeded - process the credential request
|
||||
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)")
|
||||
|
||||
// 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 8, 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]
|
||||
))
|
||||
}
|
||||
// This shouldn't happen as we check both above, but handle gracefully
|
||||
self.extensionContext.cancelRequest(withError: NSError(
|
||||
domain: ASExtensionErrorDomain,
|
||||
code: ASExtensionError.failed.rawValue,
|
||||
userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("auth_required_message", comment: "Please enable Face ID or PIN unlock in the main AliasVault app to use autofill.")]
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ struct UnlockCoordinatorView: View {
|
||||
// Show PIN unlock view
|
||||
PinUnlockView(viewModel: pinViewModel)
|
||||
} else {
|
||||
// Transparent for biometric unlock - Face ID appears with no background
|
||||
Color.clear
|
||||
.ignoresSafeArea()
|
||||
// Show logo placeholder while biometric unlock is in progress
|
||||
BiometricUnlockPlaceholderView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -37,6 +36,29 @@ struct UnlockCoordinatorView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple placeholder view showing the AliasVault logo while biometric authentication is in progress
|
||||
struct BiometricUnlockPlaceholderView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
private var colors: ColorConstants.Colors.Type {
|
||||
ColorConstants.colors(for: colorScheme)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
colors.background
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack {
|
||||
Image("Logo", bundle: .vaultUI)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 70, height: 70)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Coordinator that manages the unlock flow logic
|
||||
@MainActor
|
||||
class UnlockCoordinator: ObservableObject {
|
||||
@@ -57,18 +79,20 @@ class UnlockCoordinator: ObservableObject {
|
||||
}
|
||||
|
||||
func startUnlockFlow() {
|
||||
// Check if PIN is enabled
|
||||
// Check which auth methods are enabled
|
||||
// Priority: Biometric -> PIN -> Cancel
|
||||
// Biometrics takes priority, PIN serves as fallback if biometrics fails or is unavailable.
|
||||
let pinEnabled = vaultStore.isPinEnabled()
|
||||
let biometricEnabled = vaultStore.isBiometricAuthEnabled()
|
||||
|
||||
if pinEnabled {
|
||||
// PIN is enabled - show PIN unlock view
|
||||
createPinViewModel()
|
||||
} else if biometricEnabled {
|
||||
// Only biometric is enabled - attempt biometric unlock
|
||||
if biometricEnabled {
|
||||
// Biometric is enabled - attempt biometric unlock first
|
||||
Task {
|
||||
await attemptBiometricUnlock()
|
||||
}
|
||||
} else if pinEnabled {
|
||||
// Only PIN is enabled - show PIN unlock view
|
||||
createPinViewModel()
|
||||
} else {
|
||||
// No auth method enabled - this shouldn't happen, but cancel the request
|
||||
cancel()
|
||||
|
||||
Reference in New Issue
Block a user