diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html
index 1d39bcd0e9..b5f50841e1 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html
@@ -1,100 +1,67 @@
-
+ bitIconButton="bwi-generate"
+ bitSuffix
+ [appA11yTitle]="'generatePassword' | i18n"
+ (click)="generatePassword()"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts
index ce605a6f5a..46d0c55094 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts
@@ -1,16 +1,9 @@
-import {
- Component,
- EventEmitter,
- Input,
- OnDestroy,
- OnInit,
- Output,
- ViewChild,
-} from "@angular/core";
+import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
+import { Component, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core";
+import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
-import zxcvbn from "zxcvbn";
-import { PasswordStrengthComponent } from "@bitwarden/angular/tools/password-strength/password-strength.component";
+import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -22,27 +15,60 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac
import { OrganizationUserResetPasswordService } from "../services/organization-user-reset-password/organization-user-reset-password.service";
+/**
+ * Encapsulates a few key data inputs needed to initiate an account recovery
+ * process for the organization user in question.
+ */
+export type ResetPasswordDialogData = {
+ /**
+ * The organization user's full name
+ */
+ name: string;
+
+ /**
+ * The organization user's email address
+ */
+ email: string;
+
+ /**
+ * The `organizationUserId` for the user
+ */
+ id: string;
+
+ /**
+ * The organization's `organizationId`
+ */
+ organizationId: string;
+};
+
+export enum ResetPasswordDialogResult {
+ Ok = "ok",
+}
+
@Component({
selector: "app-reset-password",
templateUrl: "reset-password.component.html",
})
+/**
+ * Used in a dialog for initiating the account recovery process against a
+ * given organization user. An admin will access this form when they want to
+ * reset a user's password and log them out of sessions.
+ */
export class ResetPasswordComponent implements OnInit, OnDestroy {
- @Input() name: string;
- @Input() email: string;
- @Input() id: string;
- @Input() organizationId: string;
- @Output() passwordReset = new EventEmitter();
- @ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent;
+ formGroup = this.formBuilder.group({
+ newPassword: ["", Validators.required],
+ });
+
+ @ViewChild(PasswordStrengthV2Component) passwordStrengthComponent: PasswordStrengthV2Component;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
- newPassword: string = null;
showPassword = false;
- passwordStrengthResult: zxcvbn.ZXCVBNResult;
- formPromise: Promise;
+ passwordStrengthScore: number;
private destroy$ = new Subject();
constructor(
+ @Inject(DIALOG_DATA) protected data: ResetPasswordDialogData,
private resetPasswordService: OrganizationUserResetPasswordService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
@@ -51,6 +77,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
private logService: LogService,
private dialogService: DialogService,
private toastService: ToastService,
+ private formBuilder: FormBuilder,
+ private dialogRef: DialogRef,
) {}
async ngOnInit() {
@@ -69,13 +97,15 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
}
get loggedOutWarningName() {
- return this.name != null ? this.name : this.i18nService.t("thisUser");
+ return this.data.name != null ? this.data.name : this.i18nService.t("thisUser");
}
async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
- this.newPassword = await this.passwordGenerationService.generatePassword(options);
- this.passwordStrengthComponent.updatePasswordStrength(this.newPassword);
+ this.formGroup.patchValue({
+ newPassword: await this.passwordGenerationService.generatePassword(options),
+ });
+ this.passwordStrengthComponent.updatePasswordStrength(this.formGroup.value.newPassword);
}
togglePassword() {
@@ -83,7 +113,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
document.getElementById("newPassword").focus();
}
- copy(value: string) {
+ copy() {
+ const value = this.formGroup.value.newPassword;
if (value == null) {
return;
}
@@ -96,9 +127,9 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
});
}
- async submit() {
+ submit = async () => {
// Validation
- if (this.newPassword == null || this.newPassword === "") {
+ if (this.formGroup.value.newPassword == null || this.formGroup.value.newPassword === "") {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
@@ -107,7 +138,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
return false;
}
- if (this.newPassword.length < Utils.minimumPasswordLength) {
+ if (this.formGroup.value.newPassword.length < Utils.minimumPasswordLength) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
@@ -119,8 +150,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
- this.passwordStrengthResult.score,
- this.newPassword,
+ this.passwordStrengthScore,
+ this.formGroup.value.newPassword,
this.enforcedPolicyOptions,
)
) {
@@ -132,7 +163,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
return;
}
- if (this.passwordStrengthResult.score < 3) {
+ if (this.passwordStrengthScore < 3) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
@@ -145,26 +176,29 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
}
try {
- this.formPromise = this.resetPasswordService.resetMasterPassword(
- this.newPassword,
- this.email,
- this.id,
- this.organizationId,
+ await this.resetPasswordService.resetMasterPassword(
+ this.formGroup.value.newPassword,
+ this.data.email,
+ this.data.id,
+ this.data.organizationId,
);
- await this.formPromise;
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("resetPasswordSuccess"),
});
- this.passwordReset.emit();
} catch (e) {
this.logService.error(e);
}
- this.formPromise = null;
+
+ this.dialogRef.close(ResetPasswordDialogResult.Ok);
+ };
+
+ getStrengthScore(result: number) {
+ this.passwordStrengthScore = result;
}
- getStrengthResult(result: zxcvbn.ZXCVBNResult) {
- this.passwordStrengthResult = result;
- }
+ static open = (dialogService: DialogService, input: DialogConfig) => {
+ return dialogService.open(ResetPasswordComponent, input);
+ };
}
diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts
index 698c260632..3cc73c84a9 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts
@@ -70,7 +70,10 @@ import {
MemberDialogTab,
openUserAddEditDialog,
} from "./components/member-dialog";
-import { ResetPasswordComponent } from "./components/reset-password.component";
+import {
+ ResetPasswordComponent,
+ ResetPasswordDialogResult,
+} from "./components/reset-password.component";
class MembersTableDataSource extends PeopleTableDataSource {
protected statusType = OrganizationUserStatusType;
@@ -663,24 +666,19 @@ export class MembersComponent extends BaseMembersComponent
}
async resetPassword(user: OrganizationUserView) {
- const [modal] = await this.modalService.openViewRef(
- ResetPasswordComponent,
- this.resetPasswordModalRef,
- (comp) => {
- comp.name = this.userNamePipe.transform(user);
- comp.email = user != null ? user.email : null;
- comp.organizationId = this.organization.id;
- comp.id = user != null ? user.id : null;
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- comp.passwordReset.subscribe(() => {
- modal.close();
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.load();
- });
+ const dialogRef = ResetPasswordComponent.open(this.dialogService, {
+ data: {
+ name: this.userNamePipe.transform(user),
+ email: user != null ? user.email : null,
+ organizationId: this.organization.id,
+ id: user != null ? user.id : null,
},
- );
+ });
+
+ const result = await lastValueFrom(dialogRef.closed);
+ if (result === ResetPasswordDialogResult.Ok) {
+ await this.load();
+ }
}
protected async removeUserConfirmationDialog(user: OrganizationUserView) {
diff --git a/apps/web/src/app/admin-console/organizations/members/members.module.ts b/apps/web/src/app/admin-console/organizations/members/members.module.ts
index 6e8870a675..d849b1f1f3 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.module.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members.module.ts
@@ -1,6 +1,7 @@
import { ScrollingModule } from "@angular/cdk/scrolling";
import { NgModule } from "@angular/core";
+import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
import { LooseComponentsModule } from "../../../shared";
@@ -24,6 +25,7 @@ import { MembersComponent } from "./members.component";
UserDialogModule,
PasswordCalloutComponent,
ScrollingModule,
+ PasswordStrengthV2Component,
],
declarations: [
BulkConfirmComponent,