Skip to content

[PM-18721][PM-21272] Integrate InputPasswordComponent in AccountRecoveryDialogComponent #14662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: auth/pm-18721/integrate-input-password-component-in-dialogs
Choose a base branch
from

Conversation

rr-bw
Copy link
Contributor

@rr-bw rr-bw commented May 7, 2025

🎟️ Tracking

PM-18721
PM-21272

This PR stacks on top of these two PRs:

📔 Objective

This PR integrates the InputPasswordComponent within the AccountRecoveryDialogComponent (formerly called ResetPasswordComponent).

The result is that:

  • The InputPasswordComponent will now handle form field UI display and validation that is common to all of our set/change password flows
  • The AccountRecoveryDialogComponent will now just be responsible for displaying the dialog and calling resetPasswordService.resetMasterPassword() to perform the password reset operation.

The showing of the new dialog component is behind the PM16117_ChangeExistingPasswordRefactor feature flag.

📸 Screenshots

PM16117_ChangeExistingPasswordRefactor flag ON ✅

account-recovery-flag-on.mov

PM16117_ChangeExistingPasswordRefactor flag OFF ❌

account-recovery-flag-off.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

Copy link

codecov bot commented May 7, 2025

Codecov Report

Attention: Patch coverage is 0% with 61 lines in your changes missing coverage. Please review.

Project coverage is 36.79%. Comparing base (18b7d46) to head (d562acc).
Report is 34 commits behind head on auth/pm-18721/integrate-input-password-component-in-dialogs.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ount-recovery/account-recovery-dialog.component.ts 0.00% 47 Missing ⚠️
...console/organizations/members/members.component.ts 0.00% 12 Missing ⚠️
...app/admin-console/common/base-members.component.ts 0.00% 1 Missing ⚠️
...tions/members/components/account-recovery/index.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@                                       Coverage Diff                                       @@
##           auth/pm-18721/integrate-input-password-component-in-dialogs   #14662      +/-   ##
===============================================================================================
+ Coverage                                                        36.26%   36.79%   +0.52%     
===============================================================================================
  Files                                                             3203     3202       -1     
  Lines                                                            93461    92906     -555     
  Branches                                                         16864    13950    -2914     
===============================================================================================
+ Hits                                                             33897    34184     +287     
- Misses                                                           57115    57316     +201     
+ Partials                                                          2449     1406    -1043     

☔ 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 changed the base branch from main to auth/pm-18721/integrate-input-password-component-in-dialogs May 7, 2025 07:04
Copy link
Contributor

github-actions bot commented May 7, 2025

Logo
Checkmarx One – Scan Summary & Detailsbf9609a8-db1b-4021-9bab-cbf6a84a3036

New Issues (3)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM Cxbb85e86c-2fac Npm-esbuild-0.23.0
detailsRecommended version: 0.25.0
Description: esbuild is an extremely fast bundler for the web, allowing any website to send any request to the development server and read the response due to d...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: F%2Fgr9uMAqxJyLibeUtBDNowYER4I%2B3OAMHUxeFYuTvI%3D
Vulnerable Package
MEDIUM Cxbb85e86c-2fac Npm-esbuild-0.21.5
detailsRecommended version: 0.25.0
Description: esbuild is an extremely fast bundler for the web, allowing any website to send any request to the development server and read the response due to d...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: GA1sDg9c%2B7CNB%2BshyjM1MqbpTP6R5aa%2Fjurbt6HYTzU%3D
Vulnerable Package
MEDIUM Cxbb85e86c-2fac Npm-esbuild-wasm-0.23.0
detailsRecommended version: 0.25.0
Description: esbuild is an extremely fast bundler for the web, allowing any website to send any request to the development server and read the response due to d...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: Xw%2FLg2nMZISvbniEoXOWe5IvnyW%2BNqqWzgrq2DQfkZE%3D
Vulnerable Package

@rr-bw rr-bw changed the title [PM-18721] Integrate InputPasswordComponent in AccountRecoveryDialogComponent [PM-18721][PM-21272] Integrate InputPasswordComponent in AccountRecoveryDialogComponent May 7, 2025
@pamperer562580892423
Copy link

Mere mortal user here again. - I don't know if it's intentional, but the password generator component in the "Recover account" dialog (shown in the first 20 seconds of the video) doesn't contain a "Check for Data breaches"-button, like it is shown and available in the browser extension after a password was generated:

2025-05-07--09-59-38-vivaldi_EhbU414Rs6

@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs branch from 9f03aca to 22c813d Compare May 7, 2025 22:14
@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs branch from 2d30ad6 to 8c557cc Compare May 13, 2025 20:52
@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs-part-2 branch 2 times, most recently from bbd56db to 64afc78 Compare May 14, 2025 09:56
@rr-bw rr-bw marked this pull request as ready for review May 14, 2025 11:39
@rr-bw rr-bw requested review from a team as code owners May 14, 2025 11:39
Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

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

What's the plan for managing your branches - are they being merged into main one-at-a-time in order, or are they all getting merged down into a single feature branch? I ask because usually feature flags remove the need for large feature branches.

EDIT: also, please review CI failures.

inputPasswordComponent!: InputPasswordComponent;

inputPasswordFlow = InputPasswordFlow.ChangePasswordDelegation;
masterPasswordPolicyOptions?: MasterPasswordPolicyOptions;
Copy link
Member

Choose a reason for hiding this comment

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

masterPasswordPolicyOptions is now unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 111 to 123
handlePrimaryButtonClick = async () => {
try {
this.submitting = true;
await this.inputPasswordComponent.submit();
} catch {
// Flip to false if submit() throws an error
this.submitting = false;
}

// Flip to false if submit() returns early without a PasswordInputResult
// emission due to form validation errors or new password doesn't meet org policy reqs.
if (!this.receivedPasswordInputResult) {
this.submitting = false;
}
};
Copy link
Member

Choose a reason for hiding this comment

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

There is a potential race condition here: inputPasswordComponent.submit() ends by emitting an event that calls handlePasswordFormSubmit, not by calling it directly. That's good design, but it means that await this.inputPasswordComponent.submit() is not going to await the execution of handlePasswordFormSubmit as well. The race condition is whether or not receivedPasswordInputResult is updated by handlePasswordFormSubmit before it's read by handlePrimaryButtonClick.

This also means that this.submitting is not going to be accurate, although it's possible that it's near enough at the moment (especially running locally with zero latency) that it's not noticeable.

This all seems to be in service of the this.submitting behaviour, but we avoid this pattern these days in favour of the Async Actions tools in the component library. So I actually think you can avoid this problem altogether by using the CL.

Your parent-child situation is slightly unusual so if the CL isn't helping, I recommend reaching out to the UI Foundations team to see how to adapt it to your situation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I posted on the UIF team channel and it looks like Async Actions won't handle this. You can see that thread here if you'd like.

So our solution is to handle parent/child submit state reactively, like so: 1fccdde

Comment on lines 24 to 32
<span
[ngClass]="{ 'tw-invisible': !submitting }"
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center"
>
<i class="bwi bwi-spinner bwi-lg bwi-spin" aria-hidden="true"></i>
</span>
<span [ngClass]="{ 'tw-invisible': submitting }">
{{ "save" | i18n }}
</span>
Copy link
Member

Choose a reason for hiding this comment

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

Similarly, I think the CL should replace all of this manual handling around the loading spinner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See my comment above: #14662 (comment)

if (result === AccountRecoveryDialogResultTypes.Ok) {
await this.load();
}
} else {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: please use an early return rather than nesting with else.

Copy link
Contributor Author

@rr-bw rr-bw May 30, 2025

Choose a reason for hiding this comment

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

53d4a14?diff=split&w=1 (whitespace hidden)

Screenshot 2025-05-30 at 3 45 46 PM

@rr-bw rr-bw marked this pull request as draft May 21, 2025 20:13
@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs-part-2 branch from 63418d0 to c4985a9 Compare May 22, 2025 17:51
@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs branch from 6fcd9f6 to e8cbcb0 Compare May 28, 2025 08:46
@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs-part-2 branch from c4985a9 to 1629daf Compare May 28, 2025 16:00
@rr-bw rr-bw force-pushed the auth/pm-18721/integrate-input-password-component-in-dialogs-part-2 branch from 4806bf4 to 801f5b1 Compare May 30, 2025 21:25
@rr-bw rr-bw marked this pull request as ready for review May 30, 2025 22:52
@rr-bw rr-bw requested a review from eliykat May 30, 2025 22:52
@rr-bw
Copy link
Contributor Author

rr-bw commented May 30, 2025

@eliykat

What's the plan for managing your branches - are they being merged into main one-at-a-time in order, or are they all getting merged down into a single feature branch? I ask because usually feature flags remove the need for large feature branches.

Comment on lines +85 to +93
private parentSubmittingBehaviorSubject = new BehaviorSubject(false);
parentSubmitting$ = this.parentSubmittingBehaviorSubject.asObservable();

private childSubmittingBehaviorSubject = new BehaviorSubject(false);
childSubmitting$ = this.childSubmittingBehaviorSubject.asObservable();

submitting$ = combineLatest([this.parentSubmitting$, this.childSubmitting$]).pipe(
map(([parentIsSubmitting, childIsSubmitting]) => parentIsSubmitting || childIsSubmitting),
);
Copy link
Member

Choose a reason for hiding this comment

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

You've fixed the race condition, but I think this is still a lot of boilerplate just to handle a form submission. I assume this would have to be repeated in every parent component that wants to use this child component.

Does the child need to know about the form submit state at all? The submission process starts when handlePrimaryButtonClick is called, and it ends when handlePasswordFormSubmit is finished, or if the child emits an error. If that's the case, the parent should be able to handle all of that by itself, which is also better design because the parent owns the form.


💭 I also like the suggestion in Slack:

Directly referencing a component is highly discouraged. Could you instead invert the relationship so that the action happens in the parent?

It does feel a bit unusual that the child is handling the parent's submit action. There's quite a bit of logic in that child component, what if it were extracted to a service that the parent could call (thereby handling its own form submission), and the child is more presentational, simply emitting form values? That's a fairly large change so it doesn't need to be done in this PR (if at all), just mentioning it as feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The submission process starts when handlePrimaryButtonClick is called, and it ends when handlePasswordFormSubmit is finished, or if the child emits an error. If that's the case, the parent should be able to handle all of that by itself

The submit process can also end if the child returns early, without emitting. That means handlePasswordFormSubmit would never get called in the parent, leaving is in a perpetual "submitting" state.

That is, if we merely set submitting to true in handlePrimaryButtonClick, and then set it to false in handlePasswordFormSubmit, then that false set would never get called in a case where InputPasswordComponent.submit() returns early without emitting. That's the case I was trying to solve in my previous implementation here (but had the race condition).

cc: @JaredSnider-Bitwarden let me know if you have any other thoughts on this.

Copy link
Contributor Author

@rr-bw rr-bw Jun 3, 2025

Choose a reason for hiding this comment

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

There's quite a bit of logic in that child component, what if it were extracted to a service that the parent could call (thereby handling its own form submission), and the child is more presentational, simply emitting form values? That's a fairly large change so it doesn't need to be done in this PR (if at all), just mentioning it as feedback.

Yea the InputPasswordComponent has grown in scope over time. I will do a retro soon to investigate if/how we could potentially refactor things.

Copy link

sonarqubecloud bot commented Jun 4, 2025

@rr-bw
Copy link
Contributor Author

rr-bw commented Jun 4, 2025

Mere mortal user here again. - I don't know if it's intentional, but the password generator component in the "Recover account" dialog (shown in the first 20 seconds of the video) doesn't contain a "Check for Data breaches"-button, like it is shown and available in the browser extension after a password was generated:

2025-05-07--09-59-38-vivaldi_EhbU414Rs6

Thanks again! @pamperer562580892423

I've raised the question to our Product team.

@@ -96,7 +96,7 @@ export class OrganizationUserResetPasswordService
newMasterPassword: string,
email: string,
orgUserId: string,
orgId: string,
orgId: OrganizationId,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@eliykat

When I updated the organizationId property type from string to OrganizationId in the new AccountRecoveryDialogComponent (here), it meant I have to update the param type here in the resetPassword() method as well. Since that method is also used by the old ResetPasswordComponent, it means I had to make some changes to the old code (here and here).

Since I'd rather changes not leak outside the feature flag, would it be better for me to set the param type in resetPassword() to be orgId: string | OrganizationId -- and then cleanup when the flag is removed?

@rr-bw rr-bw requested a review from eliykat June 4, 2025 21:31
@pamperer562580892423
Copy link

Mere mortal user here again. - I don't know if it's intentional, but the password generator component in the "Recover account" dialog (shown in the first 20 seconds of the video) doesn't contain a "Check for Data breaches"-button, like it is shown and available in the browser extension after a password was generated:
2025-05-07--09-59-38-vivaldi_EhbU414Rs6

Thanks again! @pamperer562580892423

I've raised the question to our Product team.

Thanks for that "update"! - And I think you have also seen by now, that the "Data breach checker" is also missing in the "Update Master Password" dialog (second part of the video).

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.

3 participants