Skip to content

Conversation

@rr-bw
Copy link
Contributor

@rr-bw rr-bw commented Oct 16, 2025

🎟️ Tracking

PM-26209

📔 Objective

Update Desktop to use the AuthRequestAnsweringService, bringing it into feature parity with the Extension.

  • This also resolves the bug where an auth request coming in for an inactive account caused an error (see Jira description).
  • It also resolves a bug where a locked user would be shown a login approval dialog (see PM-26815)

📸 Screenshares

Note: OS notifications don't show while recording, which is why I'm clicking the menu to show the OS notifications in the recordings below.

Basic Login Scenario

basic-login.mov

Active User Scenarios

User is Active and Locked

active-locked.mov

User is Active and Locked (window is hidden)

active-locked-window-hidden.mov

User is Active and Unlocked

active-unlocked.mov

User is Active and Unlocked (window is hidden)

active-unlocked-window-hidden.mov

Inactive User Scenarios

User is Inactive and Locked

inactive-locked.mov

User is Inactive and Locked (window is hidden)

inactive-locked-window-hidden.mov

User is Inactive and Unlocked

inactive-unlocked.mov

User is Inactive and Unlocked (window is hidden)

inactive-unlocked-window-hidden.mov

Auth request gets sent for a user. That user then authenticates but is prompted to change password upon login

login-but-required-to-change-password.mov

User is already authenticated and on change password screen when an auth request comes in

unlocked-but-required-to-change-password.mov

⏰ 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

@github-actions
Copy link
Contributor

github-actions bot commented Oct 16, 2025

Logo
Checkmarx One – Scan Summary & Details1f9d4bc1-712e-4c1b-a258-212618494feb

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

@claude
Copy link

claude bot commented Oct 17, 2025

Claude finished @rr-bw's task —— View job


Code Review Complete

This PR successfully implements AuthRequestAnsweringService on Desktop, bringing it to feature parity with the Extension. The implementation is well-structured, thoroughly tested, and addresses the bugs mentioned in PM-26209 and PM-26815.

Summary

All previous Claude findings have been addressed appropriately by the author. The implementation follows Angular best practices, uses RxJS observables correctly, and maintains proper separation of concerns between Desktop, Browser, and common library code.

Key improvements:

  • Proper error handling added throughout
  • Race condition prevention via do/while loop pattern
  • Null safety checks for account lookups
  • Comprehensive test coverage (472 test lines in default service alone)
  • Clear separation between platform-specific implementations

Findings

Finding 1: Optional method pattern creates potential runtime issues

The receivedPendingAuthRequest? optional method in the abstraction requires runtime checks at call sites:

// In default-server-notifications.service.ts:298-306
if (this.authRequestAnsweringService.receivedPendingAuthRequest) {
  try {
    await this.authRequestAnsweringService.receivedPendingAuthRequest(...)
  } catch (error) {
    this.logService.error(`Failed to process auth request notification: ${error}`);
  }
}

This pattern works but has trade-offs:

  • ✅ Allows gradual implementation across clients
  • ✅ Graceful fallback for clients without the method
  • ⚠️ Requires discipline to check existence at every call site
  • ⚠️ Type system doesn't enforce the check

Alternative approaches considered:

  1. Separate interfaces - BasicAuthRequestAnsweringService vs AdvancedAuthRequestAnsweringService
  2. Base implementation throwing NotImplementedError - Makes the contract explicit
  3. Two separate services - AuthRequestAnsweringService and AuthRequestNotificationService

Recommendation: Current approach is acceptable given the cross-platform nature of the codebase. The try-catch error handling provides adequate safety. However, if more optional methods are added in the future, consider refactoring to separate interfaces.


Finding 2: Code duplication between Desktop and Browser app components

The openLoginApproval message handling logic is duplicated in:

  • apps/desktop/src/app/app.component.ts:508-533
  • apps/browser/src/popup/app.component.ts:200-225

Both implement identical do/while loop patterns with the same flags (processingPendingAuthRequests, shouldRerunAuthRequestProcessing). The processPendingAuthRequests() methods are also nearly identical (lines 916-949 in Desktop, 364-397 in Browser).

Impact:

  • Future changes require updates in multiple locations
  • Increases maintenance burden and test surface area
  • Risk of behavior divergence between clients

Mitigation options:

  1. Extract common logic into a shared service/utility
  2. Accept duplication given Angular component-specific dialog handling
  3. Document the duplication for future refactoring

Author's response indicates this is a known trade-off accepted for scope management. The duplication is documented in the review history.


Finding 3: Dialog dismissal state management is intentional

Previous review suggested clearing pending state when dialogs are dismissed without response. Author clarified this is intentional behavior:

Only clear the markers for the user if all of their auth requests have been specifically responded to with an approve or deny.

This design ensures:

  • Pending requests persist until explicitly addressed
  • User cannot accidentally lose requests by closing dialogs
  • Requests remain visible until timeout (15 minutes)

This is the correct behavior for security-sensitive operations.


Positive Observations

  1. Excellent test coverage - 472 lines of tests for default service, 271 for Desktop, 237 for Extension
  2. Proper RxJS usage - Observable chains use correct operators, proper cleanup with takeUntil(destroy$)
  3. Security-first design - Multiple checks prevent showing dialogs to wrong users or in wrong states
  4. Clear documentation - Comments explain non-obvious behavior (e.g., vault-v2.component.ts:1011-1019)
  5. Incremental refactor - Legacy code removed cleanly without expanding scope unnecessarily
  6. Error handling - All async operations wrapped in try-catch with logging

Checklist for Author

  • Previous Claude findings addressed with commits or explanations
  • Test coverage added for new functionality
  • Error handling implemented throughout
  • Security checks prevent unauthorized access
  • Observable cleanup handled properly
  • Documentation explains non-obvious behavior

Recommendation

Approve with observations noted. The code is production-ready. The findings above are architectural observations rather than blocking issues. Human reviewers have already approved, and all technical concerns have been addressed.

The optional method pattern (Finding 1) and code duplication (Finding 2) are acceptable trade-offs given the constraints. Consider addressing these in future refactoring if the patterns expand to more methods or clients.

@sonarqubecloud
Copy link

@codecov
Copy link

codecov bot commented Oct 20, 2025

Codecov Report

❌ Patch coverage is 53.57143% with 91 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.81%. Comparing base (e88720d) to head (aecdf3c).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
apps/desktop/src/app/app.component.ts 0.00% 32 Missing ⚠️
apps/browser/src/popup/app.component.ts 0.00% 28 Missing ⚠️
.../desktop/src/vault/app/vault/vault-v2.component.ts 0.00% 7 Missing ⚠️
apps/desktop/src/app/services/services.module.ts 0.00% 4 Missing ⚠️
...t-answering/noop-auth-request-answering.service.ts 0.00% 4 Missing ⚠️
...wering/extension-auth-request-answering.service.ts 92.10% 3 Missing ⚠️
...nswering/desktop-auth-request-answering.service.ts 88.46% 3 Missing ⚠️
...nswering/default-auth-request-answering.service.ts 92.85% 2 Missing and 1 partial ⚠️
apps/browser/src/popup/services/services.module.ts 0.00% 2 Missing ⚠️
libs/angular/src/services/jslib-services.module.ts 0.00% 2 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #16906      +/-   ##
==========================================
+ Coverage   40.79%   40.81%   +0.02%     
==========================================
  Files        3542     3541       -1     
  Lines      101502   101583      +81     
  Branches    15222    15237      +15     
==========================================
+ Hits        41403    41458      +55     
- Misses      58349    58375      +26     
  Partials     1750     1750              

☔ 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.

@rr-bw rr-bw marked this pull request as ready for review October 20, 2025 22:47
@rr-bw rr-bw requested review from a team as code owners October 20, 2025 22:47
coroiu
coroiu previously approved these changes Oct 21, 2025
Copy link
Contributor

@coroiu coroiu left a comment

Choose a reason for hiding this comment

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

No real changes to platform code, approving

nick-livefront
nick-livefront previously approved these changes Oct 22, 2025
Copy link
Collaborator

@nick-livefront nick-livefront left a comment

Choose a reason for hiding this comment

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

Only deletion of vault code ✅

Claude is calling out two issues above that are worth glancing through

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Overall excellent work, I have just one outstanding question I've pinged you on slack. Thank you for extending the feature set to the desktop.

@rr-bw rr-bw marked this pull request as draft November 10, 2025 19:47
@rr-bw rr-bw marked this pull request as ready for review November 11, 2025 23:40
Comment on lines +930 to +938
const result = await firstValueFrom(dialogRef.closed);

if (result !== undefined && typeof result === "boolean") {
respondedIds.add(req.id);

if (respondedIds.size === pendingList.length && this.activeUserId != null) {
await this.pendingAuthRequestsState.clear(this.activeUserId);
}
}
Copy link

Choose a reason for hiding this comment

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

Finding 1: Incomplete state cleanup on dialog dismissal

The pending auth state is only cleared when all requests receive a boolean response. If a user dismisses the dialog without responding (e.g., ESC key, clicking outside), result will be undefined and the pending marker remains in state until the 15-minute timeout.

Suggested fix:

const respondedIds = new Set<string>();
const dismissedIds = new Set<string>();

for (const req of pendingList) {
  if (req?.id == null) {
    continue;
  }

  const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
    notificationId: req.id,
  });

  const result = await firstValueFrom(dialogRef.closed);

  if (result !== undefined && typeof result === "boolean") {
    respondedIds.add(req.id);
  } else {
    // Dialog dismissed without response - still remove from pending state
    dismissedIds.add(req.id);
  }

  // Clear state when all requests have been either responded to or dismissed
  if (respondedIds.size + dismissedIds.size === pendingList.length && this.activeUserId != null) {
    await this.pendingAuthRequestsState.clear(this.activeUserId);
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a user dismisses the dialog without responding (e.g., ESC key, clicking outside), result will be undefined and the pending marker remains.

This is actually what we want. Only clear the markers for the user if all of their auth requests have been specifically responded to with an approve or deny.

Comment on lines 298 to 302
if (this.authRequestAnsweringService.receivedPendingAuthRequest) {
await this.authRequestAnsweringService.receivedPendingAuthRequest(
notification.payload.userId,
notification.payload.id,
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Finding 2: Missing error handling in notification processing

If receivedPendingAuthRequest throws an error, notification processing fails silently. Add try-catch for robustness:

if (this.authRequestAnsweringService.receivedPendingAuthRequest) {
  try {
    await this.authRequestAnsweringService.receivedPendingAuthRequest(
      notification.payload.userId,
      notification.payload.id,
    );
  } catch (error) {
    this.logService.error(`Failed to process auth request notification: ${error}`);
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed here: 4150ad0

Comment on lines 522 to 527
do {
this.shouldRerunAuthRequestProcessing = false;
await this.processPendingAuthRequests();
// If an "openLoginApproval" message was received while processPendingAuthRequests() was running, then
// shouldRerunAuthRequestProcessing will have been set to true
} while (this.shouldRerunAuthRequestProcessing);
Copy link

Choose a reason for hiding this comment

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

💭 Finding 3: Consider error handling in do/while loop

If processPendingAuthRequests() consistently throws errors, they may be swallowed. Consider adding error logging:

do {
  this.shouldRerunAuthRequestProcessing = false;
  try {
    await this.processPendingAuthRequests();
  } catch (error) {
    this.logService.error(`Error processing pending auth requests: ${error}`);
    // Reset flag to prevent infinite loop on persistent errors
    this.shouldRerunAuthRequestProcessing = false;
  }
} while (this.shouldRerunAuthRequestProcessing);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed here: 6c49612

);
} else {
// This call is necessary for Web, which uses a NoopAuthRequestAnsweringService
// that does not have a recievedPendingAuthRequest() method
Copy link

Choose a reason for hiding this comment

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

💭 Finding 4: Typo in comment

"recievedPendingAuthRequest" should be "receivedPendingAuthRequest"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed: 11b7304

@rr-bw rr-bw marked this pull request as draft November 11, 2025 23:49
@rr-bw rr-bw marked this pull request as ready for review November 12, 2025 19:35
@rr-bw rr-bw marked this pull request as draft November 12, 2025 20:11
@rr-bw rr-bw marked this pull request as ready for review November 13, 2025 19:49
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Thanks Ryan! Really really good work. Thank you for going so far with this.

@bw-ghapp
Copy link
Contributor

bw-ghapp bot commented Nov 20, 2025

⚠️ Changes in this PR impact the Autofill experience of the browser client ⚠️

BIT has tested the core experience with these changes and the feature flag configuration used by vault.bitwarden.com

Caution

Unfortunately, one or more of these tests failed. 😞

Please resolve the failure before merging; reach out to @bitwarden/team-autofill-dev if you'd like help.

You can view the detailed results of the tests here.

@bw-ghapp
Copy link
Contributor

bw-ghapp bot commented Nov 21, 2025

⚠️ Changes in this PR impact the Autofill experience of the browser client ⚠️

BIT has tested the core experience with these changes and all feature flags disabled.

Caution

Unfortunately, one or more of these tests failed. 😞

Please resolve the failure before merging; reach out to @bitwarden/team-autofill-dev if you'd like help.

You can view the detailed results of the tests here.

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.

5 participants