[PM-10548] fix: Biometric auth setting being reset on logout.#2628
[PM-10548] fix: Biometric auth setting being reset on logout.#2628morganzellers-bw wants to merge 16 commits into
Conversation
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the fix for biometric unlock preference being reset on logout (PM-17704 / PM-10548). The change preserves the per-user Code Review DetailsNo new findings on this pass. Substantive concerns are tracked in existing threads:
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2628 +/- ##
==========================================
- Coverage 87.36% 86.38% -0.99%
==========================================
Files 1918 2141 +223
Lines 171225 185975 +14750
==========================================
+ Hits 149596 160650 +11054
- Misses 21629 25325 +3696 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| /// | ||
| private func configureBiometricUnlockIfNeeded() async { | ||
| do { | ||
| guard try await biometricsRepository.getBiometricUnlockStatus().isEnabled else { return } |
There was a problem hiding this comment.
❓ QUESTION: When biometrics is .lockedOut, getBiometricUnlockStatus() throws BiometricsServiceError.biometryLocked, which gets caught and logged as an error here even though it's an expected transient state. This also makes the case .lockedOut: break branch in restoreBiometricUnlockKey unreachable from this caller — was that the intent?
Details and possible alternative
DefaultBiometricsRepository.getBiometricUnlockStatus throws when biometricsService.getBiometricAuthStatus() is .lockedOut (see BiometricsRepository.swift:168-170). With the current implementation, on a password unlock when biometry is temporarily locked out:
getBiometricUnlockStatus().isEnabledthrows.- The catch block logs an error via
errorReporter.log(error:), treating an expected transient OS state as an error. restoreBiometricUnlockKeyis never invoked, so its.lockedOut: breakcase is dead code from this caller.
If the goal is to silently no-op on .lockedOut (per the existing thread response on BiometricsRepository.swift:163), reading the stored preference directly side-steps the throw and lets restoreBiometricUnlockKey handle all statuses uniformly:
private func configureBiometricUnlockIfNeeded() async {
do {
guard try await stateService.getBiometricAuthenticationEnabled() else { return }
let authKey = try await clientService.crypto().getUserEncryptionKey()
try await biometricsRepository.restoreBiometricUnlockKey(authKey: authKey)
} catch {
errorReporter.log(error: error)
}
}The next unlock attempt will retry once biometry is no longer locked out, so functionality is unaffected — this is purely about log noise and reachability.
There was a problem hiding this comment.
If we decide to no-op on .lockedOut, this will fix:
private func configureBiometricUnlockIfNeeded() async {
do {
...
} catch BiometricsServiceError.biometryLocked {
// Lockout is transient; do nothing and let the user retry later.
} catch {
errorReporter.log(error: error)
}
}There was a problem hiding this comment.
I think it's worth going the no-op route to avoid logging an error here.
…nce of a KeytchainItem. Uses this to silently check for an existing biometric auth key before regenerating and storing a new one.
🎟️ Tracking
There are a few tickets covering this bug, both on iOS and Android; I've linked below the two I assigned to myself to track the fix, all others are linked to these as related items.
PM-17704
PM-10548
📔 Objective
Users' biometric unlock preference is being cleared on logout. When that user logs back in, there's no preference to dictate whether we should restore the biometric auth key value in the keychain or not.
These changes update the logout flow to leave the biometric unlock preference in place in UserDefaults on logout. Then, this is checked on login and if true, the auth key is added back to the keychain on login.
📸 Screenshots
No biometric setting on simulator for me to do a true test, but I reproduced on production iOS version 2026.4.0 (3082). Hoping a reviewer with a test device can test.