Skip to content

Conversation

@enmande
Copy link
Contributor

@enmande enmande commented Oct 30, 2025

🎟️ Tracking

PM-21204

📔 Objective

Users without a premium subscription cannot disable premium-only 2FA factors (Yubikey, Duo) that may have been configured while a premium subscription was active, either for their account or inherited through a premium organization membership.

This provides users a disable-only path to removing premium-only 2FA without an active premium subscription. The Manage action will be replaced with a Turn Off action which verifies the user and disables the specified 2FA factor. Additional configuration or management is not supported for a non-premium account.

🥇 Bonus
This includes a move/refactor of the Two-Factor Service abstraction and default implementation, including naming to align with ADRs including:

  1. Domain organization (ADR-0005, ADR-0013)
    a. Extraction of ApiService calls into a feature-centric TwoFactorApiService and TwoFactorService.
    b. Co-located models and services.
  2. File structure conventions (ADR-0011)
    a. Groups TwoFactor concerns in a central feature folder structure.
  3. TypeScript strict compliance (ADR-0014)

📸 Screenshots

User With Premium Lapsed

This user enabled a premium 2FA factor while subscribed to premium, lost premium, and is able to disable the factor.

PM-21204__mp-user-one-premium-2fa.mov

Enterprise Org Member Removed, Non-Premium User Account

Jimothy is an existing non-premium user. After being invited and confirmed to an enterprise organization (Examplecorp, a TDE organization), Jimothy enables a premium 2FA factor (Yubikey) which was made available through organization membership. Later, Jimothy is removed from the organization and consequently loses access to premium 2FA factors. Jimothy is still able to remove the 2FA factor.

PM-21204__free-user-added-removed-enterprise-org.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 30, 2025

Logo
Checkmarx One – Scan Summary & Detailsa854b9b1-3405-4f7b-8775-dd5ed7f93964

New Issues (7)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
HIGH CVE-2025-12727 Npm-electron-37.7.0
detailsDescription: Inappropriate implementation in V8 in Google Chrome prior to 142.0.7444.137 allowed a remote attacker to potentially exploit heap corruption via a ...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: Lk2veImTS7pP0CBb%2FVKZk4Jc5kCHpGdN1RuCE6%2F7kxE%3D
Vulnerable Package
HIGH CVE-2025-12907 Npm-electron-37.7.0
detailsDescription: Insufficient validation of untrusted input in Devtools in Google Chrome prior to 140.0.7339.80 allowed a remote attacker to execute arbitrary code ...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: FFrnAouowGBWljovHYUvT%2FLUJ6Pi%2Fn9o7P5mxh6tCxs%3D
Vulnerable Package
MEDIUM CVE-2025-11215 Npm-electron-37.7.0
detailsDescription: Off-by-one Error in V8 in Google Chrome prior to 141.0.7390.54 allowed a remote attacker to perform an Out-of-bounds Memory Read via a crafted HTML...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: RMzR9TCYZOkU042jHtZmuLKWj%2FtfCMvgixsDsZXHquQ%3D
Vulnerable Package
MEDIUM CVE-2025-11216 Npm-electron-37.7.0
detailsRecommended version: 39.2.0
Description: Inappropriate implementation in Storage in Google Chrome on Mac prior to 141.0.7390.54 allowed a remote attacker to perform domain spoofing via a c...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: vGI3%2FLwK0FiJ68XacebvxTcYvc%2BkJF3cMHmYqOUjO5E%3D
Vulnerable Package
MEDIUM CVE-2025-12728 Npm-electron-37.7.0
detailsDescription: Inappropriate implementation in Omnibox in Google Chrome on Android prior to 142.0.7444.137 allowed a remote attacker who convinced a user to engag...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: f4dgFrv9%2BA2gaCBAoWDllY7XWnr2JIdDeumnfw37%2FJY%3D
Vulnerable Package
MEDIUM CVE-2025-12729 Npm-electron-37.7.0
detailsDescription: Inappropriate implementation in Omnibox in Google Chrome on Android prior to 142.0.7444.137 allowed a remote attacker who convinced a user to engag...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: vFOEFVC1dSKq9uHYvue9HmC2oS6EiNX1%2F%2FX05Yo%2F%2Bnw%3D
Vulnerable Package
LOW CVE-2025-11219 Npm-electron-37.7.0
detailsRecommended version: 39.2.0
Description: Use After Free in V8 in Google Chrome prior to 141.0.7390.54 allowed a remote attacker to potentially perform Out-of-bounds Memory Access via a cra...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: 2uE8dOyxJLCPuLBlirznRMlGVXwus3YuVsFU%2BAS4kvo%3D
Vulnerable Package

@codecov
Copy link

codecov bot commented Nov 5, 2025

Codecov Report

❌ Patch coverage is 6.25000% with 165 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.23%. Comparing base (d86c918) to head (ca3bcfb).
⚠️ Report is 24 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../two-factor/services/default-two-factor.service.ts 0.00% 87 Missing ⚠️
.../settings/two-factor/two-factor-setup.component.ts 0.00% 21 Missing ⚠️
...settings/two-factor/two-factor-verify.component.ts 0.00% 9 Missing ⚠️
...auth/two-factor/abstractions/two-factor.service.ts 0.00% 8 Missing ⚠️
...ganizations/settings/two-factor-setup.component.ts 0.00% 6 Missing ⚠️
...o-factor/two-factor-setup-method-base.component.ts 0.00% 4 Missing ⚠️
.../two-factor/two-factor-setup-webauthn.component.ts 0.00% 4 Missing ⚠️
...unt/set-account-verify-devices-dialog.component.ts 0.00% 3 Missing ⚠️
...factor/two-factor-setup-authenticator.component.ts 0.00% 3 Missing ⚠️
...tings/two-factor/two-factor-setup-duo.component.ts 0.00% 3 Missing ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #17134      +/-   ##
==========================================
+ Coverage   41.20%   41.23%   +0.03%     
==========================================
  Files        3543     3545       +2     
  Lines      101912   101986      +74     
  Branches    15282    15294      +12     
==========================================
+ Hits        41988    42053      +65     
- Misses      58159    58170      +11     
+ Partials     1765     1763       -2     

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

@enmande enmande changed the title Auth/pm 21204/users without premium cannot disable premium mfa [PM-21204] Users without premium cannot disable premium 2FA Nov 5, 2025
@bitwarden bitwarden deleted a comment from claude bot Nov 5, 2025
@bitwarden bitwarden deleted a comment from claude bot Nov 5, 2025
@enmande
Copy link
Contributor Author

enmande commented Nov 6, 2025

For reviewers:
You may notice some deleted comments above from claude.
This was a result of me trying to get an early review by tagging him on the draft of this PR, but in both instances the job was cancelled with the following:

Screenshot 2025-11-06 at 11 42 54 AM

Just so it's clear there was no feedback given and removed from this review.

@enmande enmande marked this pull request as ready for review November 6, 2025 16:45
@enmande enmande requested review from a team as code owners November 6, 2025 16:45
@claude
Copy link

claude bot commented Nov 6, 2025

Claude finished @enmande's task —— View job


Code Review Complete

I've reviewed this PR addressing PM-21204 (users without premium cannot disable premium 2FA) and the accompanying two-factor service refactor. The implementation is solid and the refactor aligns well with ADRs. Here are my findings:


Summary

Finding 1: The core bugfix (disable-only flow for non-premium users) is well-implemented with clear architectural reasoning.

Finding 2: The service refactor successfully extracts API concerns and co-locates models, improving code organization.

Finding 3: QA-requested error message improvement in latest commit enhances user experience.

Finding 4: Two areas of technical debt remain acknowledged but acceptable for scope management.


Detailed Analysis

💭 Finding 1: Architectural Decision for Disable Flow

The choice to use UserVerificationDialogComponent instead of TwoFactorVerifyComponent in disablePremium2faTypeForNonPremiumUser() (apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts:163-198) is well-reasoned:

  • TwoFactorVerifyComponent.apiCall() makes GET requests requiring premium for YubiKey/Duo
  • Disable endpoint only needs user verification, not provider configuration
  • Bypasses unnecessary premium checks during the disable flow

Documentation: The inline comment clearly explains this decision, which will help future maintainers.

💭 Finding 2: Template Logic Correctness

The conditional rendering in the template (two-factor-setup.component.html:74-93) correctly implements the requirement:

@if (p.premium && p.enabled && !(canAccessPremium$ | async))

This shows the "Disable" button when:

  • Provider is premium-only (p.premium)
  • Provider is currently enabled (p.enabled)
  • User lacks premium access (!(canAccessPremium$ | async))

All other cases show the "Manage" button (disabled if user lacks premium for non-enabled premium providers).

♻️ Finding 3: TypeScript Strict Compliance Incomplete

The PR objective mentions "TypeScript strict compliance (ADR-0014)", but the new service file still has @ts-strict-ignore (libs/common/src/auth/two-factor/services/default-two-factor.service.ts:1-2).

Status: Acknowledged by enmande in review comments as intentional scope management. This is acceptable given the PR's already large scope. Consider tracking in a follow-up issue.

♻️ Finding 4: Test Coverage Gap

Codecov reports 6.25% patch coverage (165 lines missing). The new disablePremium2faTypeForNonPremiumUser() method has 0% coverage.

Status: Acknowledged by enmande as "wontfix" - component-level tests not required, service layer is tested, human verification completed. While this is a pragmatic decision, note that security-critical 2FA functionality traditionally benefits from comprehensive testing.

💭 Finding 5: Service Abstraction Quality

The refactor cleanly separates concerns:

  • TwoFactorApiService: Pure API operations (HTTP layer)
  • TwoFactorService: Client-side state management + API operations (application layer)
  • Models co-located with services (ADR-0013 compliance)

The abstraction is well-documented with comprehensive JSDoc comments explaining parameters, return values, and security requirements.

✅ Finding 6: Error Message Improvement

Latest commit (ca3bcfb) improves user experience:

  • Removed unused bodyText parameter from dialog
  • Changed generic e.message to localized "userVerificationFailed" key
  • Better error handling by not exposing raw exception messages to users

This addresses QA feedback from PM-28511 appropriately.


PR Title and Description Quality

Title: ✅ Clear and specific - describes both the bug fix and refactor scope

Description: ✅ Comprehensive

  • Clearly explains the problem and solution
  • References Jira ticket (PM-21204)
  • Includes video demonstrations of both test scenarios
  • Documents bonus refactor work with ADR references
  • Provides complete test plan via videos

Recommendations

  1. 🎨 Future improvement: Consider creating a follow-up issue to address TypeScript strict mode violations in default-two-factor.service.ts

  2. 🎨 Future improvement: The TwoFactorProviderDetails interface uses string | null for name and description because they're hydrated during init(). This works but creates temporal coupling. Consider refactoring to eliminate the initialization dependency (as suggested in review comment by enmande).

  3. 💭 Question: The canAccessPremium$ observable is evaluated at render time. If premium status changes while the page is open (e.g., subscription expires), the UI won't update until the next change detection cycle. Is this acceptable behavior or should there be an active listener?


Conclusion

This PR successfully addresses PM-21204 with a well-architected solution that respects existing patterns while improving code organization. The refactor brings meaningful ADR compliance improvements without overreaching in scope. Previous reviewers (jrmccannon, Patrick-Pimentel-Bitwarden, dereknance) have approved, and my review concurs.

The acknowledged technical debt (strict mode, test coverage) represents reasonable scope management decisions rather than oversights. The core functionality is sound, security implications are properly handled, and the user experience improvement from the latest commit demonstrates responsiveness to QA feedback.

Overall assessment: ✅ Ready for merge pending any final reviewer concerns about acknowledged technical debt.


jrmccannon
jrmccannon previously approved these changes Nov 7, 2025
Copy link
Contributor

@jrmccannon jrmccannon left a comment

Choose a reason for hiding this comment

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

👍 Looks fine from a AC standpoint. Our files were only affected by the rename.

Next time, I think it would be easier to digest if the rename/refactor was done separately so its easier to separate the logic changes from the refactor.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 The refactor looks as good as good as the bug fix! You toed the line well of just handling the partitioning without addressing more technical debt (I would have mistakenly tried to fix a couple of the @ts-strict-ignore's and I'm glad you didn't). One comment I would make though is when doing these refactors consider where you could do file moves instead of whole new file creations/deletions to maintain some of the git commit history.

dereknance
dereknance previously approved these changes Nov 7, 2025
@enmande
Copy link
Contributor Author

enmande commented Nov 10, 2025

👍 Looks fine from a AC standpoint. Our files were only affected by the rename.

Next time, I think it would be easier to digest if the rename/refactor was done separately so its easier to separate the logic changes from the refactor.

@jrmccannon Thank you for the feedback; you're not alone in having considered this through the review process. In hindsight, it was good learning and good progress toward aligning more of the codebase, but bit ambitious to combine them. At the very least this could have had a refactor PR fast-followed by the bugfix. Appreciate the extra effort on this review, and will definitely consider this more next time! 👍

@enmande enmande added the needs-qa Marks a PR as requiring QA approval label Nov 10, 2025
@enmande
Copy link
Contributor Author

enmande commented Nov 20, 2025

QA requested a small change in PM-28511 to improve the error message on a failed user verification at disablement time. Addressed in ca3bcfb.

PM-21204__user-verification-failed.mov

@enmande
Copy link
Contributor Author

enmande commented Nov 20, 2025

QA has been completed on this issue. Re-requesting reviews.

@enmande enmande removed the needs-qa Marks a PR as requiring QA approval label Nov 20, 2025
@enmande enmande merged commit daf7b7d into main Nov 21, 2025
141 of 143 checks passed
@enmande enmande deleted the auth/pm-21204/users-without-premium-cannot-disable-premium-mfa branch November 21, 2025 15:35
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.

6 participants