mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 13:28:12 -04:00
Tweak iOS native pin unlock view flow (#1340)
This commit is contained in:
committed by
Leendert de Borst
parent
e5ed8d380f
commit
7b6170e927
@@ -268,6 +268,7 @@ extension CredentialProviderViewController: PasskeyProviderDelegate {
|
||||
)
|
||||
},
|
||||
cancelHandler: { [weak self] in
|
||||
// Passkey registration - just cancel on any dismissal
|
||||
self?.extensionContext.cancelRequest(withError: NSError(
|
||||
domain: ASExtensionErrorDomain,
|
||||
code: ASExtensionError.userCanceled.rawValue
|
||||
|
||||
@@ -235,8 +235,36 @@ public class CredentialProviderViewController: ASCredentialProviderViewControlle
|
||||
}
|
||||
}
|
||||
},
|
||||
cancelHandler: { [weak self] in
|
||||
self?.handleCancel()
|
||||
cancelHandler: { [weak self] pinWasDisabled in
|
||||
guard let self = self else { return }
|
||||
|
||||
if pinWasDisabled {
|
||||
// PIN was disabled due to max attempts
|
||||
// Try biometric as fallback if available
|
||||
if vaultStore.isBiometricAuthEnabled() {
|
||||
do {
|
||||
// Try to unlock with biometric
|
||||
try vaultStore.unlockVault()
|
||||
|
||||
// Success - 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 {
|
||||
// Biometric failed - cancel
|
||||
print("Biometric fallback failed after PIN lockout: \(error)")
|
||||
self.handleCancel()
|
||||
}
|
||||
} else {
|
||||
// No other auth method - cancel
|
||||
self.handleCancel()
|
||||
}
|
||||
} else {
|
||||
// User manually cancelled
|
||||
self.handleCancel()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -525,8 +553,8 @@ public class CredentialProviderViewController: ASCredentialProviderViewControlle
|
||||
// Check if PIN is available as fallback
|
||||
if !pinEnabled {
|
||||
let alert = UIAlertController(
|
||||
title: String(format: NSLocalizedString("biometric_required", comment: ""), authMethod),
|
||||
message: String(format: NSLocalizedString("biometric_required_message", comment: ""), authMethod),
|
||||
title: String(format: NSLocalizedString("auth_required", comment: ""), authMethod),
|
||||
message: String(format: NSLocalizedString("auth_required_message", comment: ""), authMethod),
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default) { [weak self] _ in
|
||||
|
||||
@@ -100,8 +100,24 @@ class UnlockCoordinator: ObservableObject {
|
||||
self.onUnlocked()
|
||||
}
|
||||
},
|
||||
cancelHandler: { [weak self] in
|
||||
self?.cancel()
|
||||
cancelHandler: { [weak self] pinWasDisabled in
|
||||
guard let self = self else { return }
|
||||
|
||||
if pinWasDisabled {
|
||||
// PIN was disabled due to max attempts
|
||||
// Try biometric as fallback if available
|
||||
if self.vaultStore.isBiometricAuthEnabled() {
|
||||
Task {
|
||||
await self.attemptBiometricUnlock()
|
||||
}
|
||||
} else {
|
||||
// No other auth method available - cancel
|
||||
self.cancel()
|
||||
}
|
||||
} else {
|
||||
// User manually cancelled - just cancel
|
||||
self.cancel()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -859,10 +859,16 @@ public class VaultManager: NSObject {
|
||||
}
|
||||
}
|
||||
},
|
||||
cancelHandler: {
|
||||
// User cancelled
|
||||
cancelHandler: { pinWasDisabled in
|
||||
// Dismiss the view
|
||||
rootVC.dismiss(animated: true) {
|
||||
reject("USER_CANCELLED", "User cancelled PIN unlock", nil)
|
||||
if pinWasDisabled {
|
||||
// PIN was disabled due to max attempts
|
||||
reject("PIN_DISABLED", "PIN was disabled after too many failed attempts", nil)
|
||||
} else {
|
||||
// User manually cancelled
|
||||
reject("USER_CANCELLED", "User cancelled PIN unlock", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
private let locBundle = Bundle.vaultUI
|
||||
|
||||
@@ -288,12 +289,12 @@ public class PinUnlockViewModel: ObservableObject {
|
||||
|
||||
public let pinLength: Int?
|
||||
private let unlockHandler: (String) async throws -> Void
|
||||
private let cancelHandler: () -> Void
|
||||
private let cancelHandler: (Bool) -> Void // Bool indicates if PIN was disabled/locked
|
||||
|
||||
public init(
|
||||
pinLength: Int?,
|
||||
unlockHandler: @escaping (String) async throws -> Void,
|
||||
cancelHandler: @escaping () -> Void
|
||||
cancelHandler: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.pinLength = pinLength
|
||||
self.unlockHandler = unlockHandler
|
||||
@@ -328,7 +329,7 @@ public class PinUnlockViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
cancelHandler()
|
||||
cancelHandler(false) // User manually cancelled
|
||||
}
|
||||
|
||||
private func attemptUnlock() async {
|
||||
@@ -341,18 +342,36 @@ public class PinUnlockViewModel: ObservableObject {
|
||||
// Success - the handler will navigate away or complete the flow
|
||||
// Keep loading state active since we're navigating
|
||||
} catch let nsError as NSError {
|
||||
// Handle unlock errors
|
||||
// Check for PIN disabled errors (error code 25)
|
||||
// This occurs when PIN was disabled due to max attempts or configuration issue
|
||||
if nsError.code == 25 {
|
||||
// PIN is no longer available - auto-dismiss the view
|
||||
// This happens when user enters wrong PIN too many times
|
||||
isUnlocking = false
|
||||
cancelHandler(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle other unlock errors (incorrect PIN, etc.)
|
||||
isUnlocking = false
|
||||
self.error = nsError.localizedDescription
|
||||
triggerErrorFeedback()
|
||||
shakeAndClear()
|
||||
} catch let genericError {
|
||||
// Generic error
|
||||
isUnlocking = false
|
||||
self.error = String(localized: "unlock_failed", bundle: locBundle)
|
||||
triggerErrorFeedback()
|
||||
shakeAndClear()
|
||||
}
|
||||
}
|
||||
|
||||
private func triggerErrorFeedback() {
|
||||
// Trigger haptic feedback for error
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.error)
|
||||
}
|
||||
|
||||
private func shakeAndClear() {
|
||||
// Clear the PIN after a short delay to show error
|
||||
Task {
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user