Skip to content

Conversation

@quexten
Copy link
Contributor

@quexten quexten commented Nov 4, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-27225

📔 Objective

When a user without a master password (TDE/key connector) logs in and has an unreliable unlock method (biometrics), if biometrics is temporarily unavailable, currently a broken component is shown. This PR detects that case and shows the biometric component, with the button being unavailable, so the user realizes they have to re-connect their biometrics device (unfold their laptop).

Further, in the case when a user cannot unlock - no password and no temporarily unavailable unlock method - we log out the user automatically.

Also adds the unavailability reason as a tooltip as per product's suggestion.

📸 Screenshots

image image

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@quexten quexten force-pushed the km/fix-biometrics-tde-unavailable branch from e1bfaf6 to 39666ad Compare November 4, 2025 13:17
@github-actions
Copy link
Contributor

github-actions bot commented Nov 4, 2025

Logo
Checkmarx One – Scan Summary & Detailsd6f32aed-431c-4d5f-9ad4-60e1c5ad37e0

Great job! No new security vulnerabilities introduced in this pull request

@quexten quexten changed the title Fix nothing showing when biometrics unavailable [PM-27225] Fix nothing showing when biometrics unavailable Nov 4, 2025
@codecov
Copy link

codecov bot commented Nov 5, 2025

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.12%. Comparing base (db16c20) to head (a6b2435).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...anagement-ui/src/lock/components/lock.component.ts 83.33% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #17209      +/-   ##
==========================================
+ Coverage   41.11%   41.12%   +0.01%     
==========================================
  Files        3543     3543              
  Lines      101843   101854      +11     
  Branches    15265    15270       +5     
==========================================
+ Hits        41872    41887      +15     
+ Misses      58208    58201       -7     
- Partials     1763     1766       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@quexten quexten marked this pull request as ready for review November 5, 2025 10:26
@quexten quexten requested a review from a team as a code owner November 5, 2025 10:26
@quexten quexten requested a review from mzieniukbw November 5, 2025 10:26
@claude
Copy link

claude bot commented Nov 5, 2025

Claude finished @quexten's task —— View job


Code Review Complete

I've reviewed the latest changes to this PR. The implementation looks solid overall, with comprehensive test coverage added. However, there is one unresolved issue from the previous Claude review that still needs to be addressed.


Findings

Finding 1: Inconsistent data source for biometric status check creates potential race condition

Location: lock.component.ts:287-290

Issue: The code calls getBiometricsStatusForUser() separately after already fetching unlockOptions, which includes the biometric status. This creates a potential race condition where the two status values could be inconsistent if the biometric state changes between the two calls.

// Line 283-285: unlockOptions already contains biometric status
this.unlockOptions = await firstValueFrom(
  this.lockComponentService.getAvailableUnlockOptions$(activeAccount.id),
);

// Line 287-290: Makes a separate call for the same status
const canUseBiometrics = [
  BiometricsStatus.Available,
  ...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(await this.biometricService.getBiometricsStatusForUser(activeAccount.id));

Suggested fix: Use the status already available in unlockOptions:

const canUseBiometrics = [
  BiometricsStatus.Available,
  ...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(this.unlockOptions?.biometrics.biometricsStatus ?? BiometricsStatus.PlatformUnsupported);

This was previously flagged in review comments and appears to still be unresolved.


Positive Observations

  • Comprehensive test coverage added for the new logic (248 lines of tests)
  • Clear separation of concerns with the BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES constant
  • Proper handling of edge cases (TDE users without unlock options)
  • Good UX improvement: showing disabled biometric button with tooltip instead of broken component
  • Tests cover all the important scenarios including forced logout when no unlock options available

Summary

The PR successfully addresses the core issue of showing a proper UI when biometrics is temporarily unavailable. The test coverage is excellent. Please address the data source inconsistency issue (Finding 1) to ensure the biometric status check is reliable and consistent with the displayed unlock options.

Comment on lines +287 to +290
const canUseBiometrics = [
BiometricsStatus.Available,
...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(await this.biometricService.getBiometricsStatusForUser(activeAccount.id));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Race condition: This makes a separate call to getBiometricsStatusForUser() after already fetching unlockOptions, which could result in inconsistent state.

As mzieniukbw suggested, use the status from unlockOptions directly:

const canUseBiometrics = [
  BiometricsStatus.Available,
  ...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(this.unlockOptions.biometrics.biometricsStatus);

This ensures the biometric status check uses the same data already fetched in unlockOptions.

Comment on lines 287 to 300
const canUseBiometrics = [
BiometricsStatus.Available,
...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(await this.biometricService.getBiometricsStatusForUser(activeAccount.id));
if (
!this.unlockOptions.masterPassword.enabled &&
!this.unlockOptions.pin.enabled &&
!canUseBiometrics
) {
// User has no available unlock options, force logout. This happens for TDE users without a masterpassword, that don't have a persistent unlock method set.
this.logService.warning("[LockComponent] User cannot unlock again. Logging out!");
await this.logoutService.logout(activeAccount.id);
return;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Code duplication: This logic overlaps significantly with VaultTimeoutSettingsService.getAvailableVaultTimeoutActions() (lines 280-289 in vault-timeout-settings.service.ts), which has similar logic:

const canLock =
  (await this.userHasMasterPassword(userId)) ||
  (await this.pinStateService.isPinSet(userId as UserId)) ||
  (await this.isBiometricLockSet(userId));

Consider extracting this to a shared utility or reusing the existing VaultTimeoutSettingsService.canLock() method to avoid maintenance burden and potential divergence between the two implementations.

@quexten quexten requested a review from mzieniukbw November 5, 2025 13:13
mzieniukbw
mzieniukbw previously approved these changes Nov 5, 2025
const canUseBiometrics = [
BiometricsStatus.Available,
...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(await this.biometricService.getBiometricsStatusForUser(activeAccount.id));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Inconsistent data source: This calls getBiometricsStatusForUser() directly after getAvailableUnlockOptions$() already fetched biometric status.

The biometric status is already available in this.unlockOptions.biometrics.biometricsStatus (line 283-285). Using a separate call could result in inconsistent state if the biometric status changes between the two calls.

Suggested change:

const canUseBiometrics = [
  BiometricsStatus.Available,
  ...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES,
].includes(this.unlockOptions?.biometrics.biometricsStatus ?? BiometricsStatus.PlatformUnsupported);

This was flagged in a previous review and appears unresolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When using a Macbook Pro with an external display connected, it cannot be unlocked with a password.

3 participants