diff --git a/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.html
index 368817a45b3..fa16ace4a0f 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.html
@@ -7,8 +7,6 @@
@@ -17,8 +15,8 @@
type="button"
bitButton
buttonType="primary"
- [disabled]="submitting$ | async"
- (click)="handlePrimaryButtonClick()"
+ bitButton
+ [bitAction]="handlePrimaryButtonClick"
>
{{ "save" | i18n }}
diff --git a/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.ts
index 42cd257e1a2..4ff478857b2 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/account-recovery/account-recovery-dialog.component.ts
@@ -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,
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`
diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts
index bd3a015055b..2ae5590ad3e 100644
--- a/libs/auth/src/angular/input-password/input-password.component.ts
+++ b/libs/auth/src/angular/input-password/input-password.component.ts
@@ -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();
@Output() onSecondaryButtonClick = new EventEmitter();
- @Output() isSubmitting = new EventEmitter();
@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 => {
+ 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 {
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);
}
/**