-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[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
base: auth/pm-18721/integrate-input-password-component-in-dialogs
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
✅ All tests successful. No failed tests found. 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. 🚀 New features to boost your workflow:
|
New Issues (3)Checkmarx found the following issues in this Pull Request
|
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: |
9f03aca
to
22c813d
Compare
...nsole/organizations/members/components/account-recovery/account-recovery-dialog.component.ts
Outdated
Show resolved
Hide resolved
...nsole/organizations/members/components/account-recovery/account-recovery-dialog.component.ts
Outdated
Show resolved
Hide resolved
...nsole/organizations/members/components/account-recovery/account-recovery-dialog.component.ts
Outdated
Show resolved
Hide resolved
2d30ad6
to
8c557cc
Compare
bbd56db
to
64afc78
Compare
There was a problem hiding this 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
masterPasswordPolicyOptions
is now unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; | ||
} | ||
}; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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)

63418d0
to
c4985a9
Compare
6fcd9f6
to
e8cbcb0
Compare
c4985a9
to
1629daf
Compare
…e observable first
4806bf4
to
801f5b1
Compare
… in InputPasswordComponent HTML
|
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), | ||
); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 whenhandlePasswordFormSubmit
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.
There was a problem hiding this comment.
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.
…ert changes that leaked outside of flag
|
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
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). |
🎟️ Tracking
PM-18721
PM-21272
This PR stacks on top of these two PRs:
📔 Objective
This PR integrates the
InputPasswordComponent
within theAccountRecoveryDialogComponent
(formerly calledResetPasswordComponent
).The result is that:
InputPasswordComponent
will now handle form field UI display and validation that is common to all of our set/change password flowsAccountRecoveryDialogComponent
will now just be responsible for displaying the dialog and callingresetPasswordService.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
🦮 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