1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 22:44:11 +00:00

Suggestion: use return value instead of events

This commit is contained in:
Thomas Rittson
2025-06-12 12:59:48 +10:00
parent d562acca17
commit 3947a4c3d5
3 changed files with 130 additions and 169 deletions

View File

@@ -7,8 +7,6 @@
<auth-input-password
[flow]="inputPasswordFlow"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions$ | async"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
(isSubmitting)="handleIsSubmittingChange($event)"
></auth-input-password>
</ng-container>
@@ -17,8 +15,8 @@
type="button"
bitButton
buttonType="primary"
[disabled]="submitting$ | async"
(click)="handlePrimaryButtonClick()"
bitButton
[bitAction]="handlePrimaryButtonClick"
>
{{ "save" | i18n }}
</button>

View File

@@ -1,19 +1,15 @@
import { CommonModule } from "@angular/common";
import { Component, Inject, ViewChild } from "@angular/core";
import { BehaviorSubject, combineLatest, map, switchMap } from "rxjs";
import { switchMap } from "rxjs";
import {
InputPasswordComponent,
InputPasswordFlow,
PasswordInputResult,
} from "@bitwarden/auth/angular";
import { InputPasswordComponent, InputPasswordFlow } from "@bitwarden/auth/angular";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
CalloutModule,
DIALOG_DATA,
@@ -76,22 +72,14 @@ export type AccountRecoveryDialogResultType =
DialogModule,
I18nPipe,
InputPasswordComponent,
ButtonModule,
AsyncActionsModule,
],
})
export class AccountRecoveryDialogComponent {
@ViewChild(InputPasswordComponent)
inputPasswordComponent: InputPasswordComponent | undefined = undefined;
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),
);
masterPasswordPolicyOptions$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
@@ -108,7 +96,6 @@ export class AccountRecoveryDialogComponent {
private accountService: AccountService,
private dialogRef: DialogRef<AccountRecoveryDialogResultType>,
private i18nService: I18nService,
private logService: LogService,
private policyService: PolicyService,
private resetPasswordService: OrganizationUserResetPasswordService,
private toastService: ToastService,
@@ -119,37 +106,26 @@ export class AccountRecoveryDialogComponent {
throw new Error("InputPasswordComponent is not initialized");
}
await this.inputPasswordComponent.submit();
};
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.parentSubmittingBehaviorSubject.next(true);
try {
await this.resetPasswordService.resetMasterPassword(
passwordInputResult.newPassword,
this.dialogData.email,
this.dialogData.organizationUserId,
this.dialogData.organizationId,
);
this.toastService.showToast({
variant: "success",
title: "",
message: this.i18nService.t("resetPasswordSuccess"),
});
} catch (e) {
this.logService.error(e);
} finally {
this.parentSubmittingBehaviorSubject.next(false);
const passwordInputResult = await this.inputPasswordComponent.submit();
if (!passwordInputResult) {
return;
}
this.dialogRef.close(AccountRecoveryDialogResultType.Ok);
}
await this.resetPasswordService.resetMasterPassword(
passwordInputResult.newPassword,
this.dialogData.email,
this.dialogData.organizationUserId,
this.dialogData.organizationId,
);
protected handleIsSubmittingChange(isSubmitting: boolean) {
this.childSubmittingBehaviorSubject.next(isSubmitting);
}
this.toastService.showToast({
variant: "success",
title: "",
message: this.i18nService.t("resetPasswordSuccess"),
});
this.dialogRef.close(AccountRecoveryDialogResultType.Ok);
};
/**
* Strongly typed helper to open an `AccountRecoveryDialogComponent`

View File

@@ -13,7 +13,6 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
@@ -121,9 +120,7 @@ export class InputPasswordComponent implements OnInit {
| PasswordStrengthV2Component
| undefined = undefined;
@Output() onPasswordFormSubmit = new EventEmitter<PasswordInputResult>();
@Output() onSecondaryButtonClick = new EventEmitter<void>();
@Output() isSubmitting = new EventEmitter<boolean>();
@Input({ required: true }) flow!: InputPasswordFlow;
@@ -192,7 +189,6 @@ export class InputPasswordComponent implements OnInit {
private platformUtilsService: PlatformUtilsService,
private policyService: PolicyService,
private toastService: ToastService,
private validationService: ValidationService,
) {}
ngOnInit(): void {
@@ -262,138 +258,129 @@ export class InputPasswordComponent implements OnInit {
}
}
submit = async () => {
try {
this.isSubmitting.emit(true);
submit = async (): Promise<PasswordInputResult | undefined> => {
this.verifyFlow();
this.verifyFlow();
this.formGroup.markAllAsTouched();
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
this.showErrorSummary = true;
return;
}
if (this.formGroup.invalid) {
this.showErrorSummary = true;
return;
const currentPassword = this.formGroup.controls.currentPassword?.value ?? "";
const newPassword = this.formGroup.controls.newPassword.value;
const newPasswordHint = this.formGroup.controls.newPasswordHint?.value ?? "";
const checkForBreaches = this.formGroup.controls.checkForBreaches?.value ?? true;
if (this.flow === InputPasswordFlow.ChangePasswordDelegation) {
return this.handleChangePasswordDelegationFlow(newPassword);
}
if (!this.email) {
throw new Error("Email is required to create master key.");
}
// 1. Determine kdfConfig
if (this.flow === InputPasswordFlow.SetInitialPasswordAccountRegistration) {
this.kdfConfig = DEFAULT_KDF_CONFIG;
} else {
if (!this.userId) {
throw new Error("userId not passed down");
}
this.kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
}
const currentPassword = this.formGroup.controls.currentPassword?.value ?? "";
const newPassword = this.formGroup.controls.newPassword.value;
const newPasswordHint = this.formGroup.controls.newPasswordHint?.value ?? "";
const checkForBreaches = this.formGroup.controls.checkForBreaches?.value ?? true;
if (this.kdfConfig == null) {
throw new Error("KdfConfig is required to create master key.");
}
if (this.flow === InputPasswordFlow.ChangePasswordDelegation) {
await this.handleChangePasswordDelegationFlow(newPassword);
return;
}
if (!this.email) {
throw new Error("Email is required to create master key.");
}
// 1. Determine kdfConfig
if (this.flow === InputPasswordFlow.SetInitialPasswordAccountRegistration) {
this.kdfConfig = DEFAULT_KDF_CONFIG;
} else {
if (!this.userId) {
throw new Error("userId not passed down");
}
this.kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
}
if (this.kdfConfig == null) {
throw new Error("KdfConfig is required to create master key.");
}
// 2. Verify current password is correct (if necessary)
if (
this.flow === InputPasswordFlow.ChangePassword ||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
const currentPasswordVerified = await this.verifyCurrentPassword(
currentPassword,
this.kdfConfig,
);
if (!currentPasswordVerified) {
return;
}
}
// 3. Verify new password
const newPasswordVerified = await this.verifyNewPassword(
newPassword,
this.passwordStrengthScore,
checkForBreaches,
// 2. Verify current password is correct (if necessary)
if (
this.flow === InputPasswordFlow.ChangePassword ||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
const currentPasswordVerified = await this.verifyCurrentPassword(
currentPassword,
this.kdfConfig,
);
if (!newPasswordVerified) {
if (!currentPasswordVerified) {
return;
}
}
// 4. Create cryptographic keys and build a PasswordInputResult object
const newMasterKey = await this.keyService.makeMasterKey(
newPassword,
// 3. Verify new password
const newPasswordVerified = await this.verifyNewPassword(
newPassword,
this.passwordStrengthScore,
checkForBreaches,
);
if (!newPasswordVerified) {
return;
}
// 4. Create cryptographic keys and build a PasswordInputResult object
const newMasterKey = await this.keyService.makeMasterKey(
newPassword,
this.email,
this.kdfConfig,
);
const newServerMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
HashPurpose.ServerAuthorization,
);
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
HashPurpose.LocalAuthorization,
);
const passwordInputResult: PasswordInputResult = {
newPassword,
newMasterKey,
newServerMasterKeyHash,
newLocalMasterKeyHash,
newPasswordHint,
kdfConfig: this.kdfConfig,
};
if (
this.flow === InputPasswordFlow.ChangePassword ||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
const currentMasterKey = await this.keyService.makeMasterKey(
currentPassword,
this.email,
this.kdfConfig,
);
const newServerMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
const currentServerMasterKeyHash = await this.keyService.hashMasterKey(
currentPassword,
currentMasterKey,
HashPurpose.ServerAuthorization,
);
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
const currentLocalMasterKeyHash = await this.keyService.hashMasterKey(
currentPassword,
currentMasterKey,
HashPurpose.LocalAuthorization,
);
const passwordInputResult: PasswordInputResult = {
newPassword,
newMasterKey,
newServerMasterKeyHash,
newLocalMasterKeyHash,
newPasswordHint,
kdfConfig: this.kdfConfig,
};
if (
this.flow === InputPasswordFlow.ChangePassword ||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
const currentMasterKey = await this.keyService.makeMasterKey(
currentPassword,
this.email,
this.kdfConfig,
);
const currentServerMasterKeyHash = await this.keyService.hashMasterKey(
currentPassword,
currentMasterKey,
HashPurpose.ServerAuthorization,
);
const currentLocalMasterKeyHash = await this.keyService.hashMasterKey(
currentPassword,
currentMasterKey,
HashPurpose.LocalAuthorization,
);
passwordInputResult.currentPassword = currentPassword;
passwordInputResult.currentMasterKey = currentMasterKey;
passwordInputResult.currentServerMasterKeyHash = currentServerMasterKeyHash;
passwordInputResult.currentLocalMasterKeyHash = currentLocalMasterKeyHash;
}
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
}
// 5. Emit cryptographic keys and other password related properties
this.onPasswordFormSubmit.emit(passwordInputResult);
} catch (e) {
this.validationService.showError(e);
} finally {
this.isSubmitting.emit(false);
passwordInputResult.currentPassword = currentPassword;
passwordInputResult.currentMasterKey = currentMasterKey;
passwordInputResult.currentServerMasterKeyHash = currentServerMasterKeyHash;
passwordInputResult.currentLocalMasterKeyHash = currentLocalMasterKeyHash;
}
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
}
// 5. Emit cryptographic keys and other password related properties
return passwordInputResult;
};
/**
@@ -442,7 +429,9 @@ export class InputPasswordComponent implements OnInit {
}
}
private async handleChangePasswordDelegationFlow(newPassword: string) {
private async handleChangePasswordDelegationFlow(
newPassword: string,
): Promise<PasswordInputResult | undefined> {
const newPasswordVerified = await this.verifyNewPassword(
newPassword,
this.passwordStrengthScore,
@@ -452,11 +441,9 @@ export class InputPasswordComponent implements OnInit {
return;
}
const passwordInputResult: PasswordInputResult = {
return {
newPassword,
};
this.onPasswordFormSubmit.emit(passwordInputResult);
}
/**