diff --git a/libs/angular/src/tools/password-strength/password-strength-v2.component.html b/libs/angular/src/tools/password-strength/password-strength-v2.component.html
new file mode 100644
index 0000000000..b613f0f274
--- /dev/null
+++ b/libs/angular/src/tools/password-strength/password-strength-v2.component.html
@@ -0,0 +1,7 @@
+
diff --git a/libs/angular/src/tools/password-strength/password-strength-v2.component.spec.ts b/libs/angular/src/tools/password-strength/password-strength-v2.component.spec.ts
new file mode 100644
index 0000000000..2a67d3b9ed
--- /dev/null
+++ b/libs/angular/src/tools/password-strength/password-strength-v2.component.spec.ts
@@ -0,0 +1,80 @@
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { mock } from "jest-mock-extended";
+
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
+
+import {
+ PasswordColorText,
+ PasswordStrengthScore,
+ PasswordStrengthV2Component,
+} from "./password-strength-v2.component";
+
+describe("PasswordStrengthV2Component", () => {
+ let component: PasswordStrengthV2Component;
+ let fixture: ComponentFixture;
+
+ const mockPasswordStrengthService = mock();
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: I18nService, useValue: { t: (key: string) => key } },
+ { provide: PasswordStrengthServiceAbstraction, useValue: mockPasswordStrengthService },
+ ],
+ });
+ fixture = TestBed.createComponent(PasswordStrengthV2Component);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create the component", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("should update password strength when password changes", () => {
+ const password = "testPassword";
+ jest.spyOn(component, "updatePasswordStrength");
+ component.password = password;
+ expect(component.updatePasswordStrength).toHaveBeenCalledWith(password);
+ expect(mockPasswordStrengthService.getPasswordStrength).toHaveBeenCalledWith(
+ password,
+ undefined,
+ undefined,
+ );
+ });
+
+ it("should emit password strength result when password changes", () => {
+ const password = "testPassword";
+ jest.spyOn(component.passwordStrengthScore, "emit");
+ component.password = password;
+ expect(component.passwordStrengthScore.emit).toHaveBeenCalled();
+ });
+
+ it("should emit password score text and color when ngOnChanges executes", () => {
+ jest.spyOn(component.passwordScoreTextWithColor, "emit");
+ jest.useFakeTimers();
+ component.ngOnChanges();
+ jest.runAllTimers();
+ expect(component.passwordScoreTextWithColor.emit).toHaveBeenCalled();
+ });
+
+ const table = [
+ [4, { color: "success", text: "strong" }],
+ [3, { color: "primary", text: "good" }],
+ [2, { color: "warning", text: "weak" }],
+ [1, { color: "danger", text: "weak" }],
+ [null, { color: "danger", text: null }],
+ ];
+
+ test.each(table)(
+ "should passwordScore be %d then emit passwordScoreTextWithColor = %s",
+ (score: PasswordStrengthScore, expected: PasswordColorText) => {
+ jest.useFakeTimers();
+ jest.spyOn(component.passwordScoreTextWithColor, "emit");
+ component.passwordScore = score;
+ component.ngOnChanges();
+ jest.runAllTimers();
+ expect(component.passwordScoreTextWithColor.emit).toHaveBeenCalledWith(expected);
+ },
+ );
+});
diff --git a/libs/angular/src/tools/password-strength/password-strength-v2.component.ts b/libs/angular/src/tools/password-strength/password-strength-v2.component.ts
new file mode 100644
index 0000000000..425adba7ba
--- /dev/null
+++ b/libs/angular/src/tools/password-strength/password-strength-v2.component.ts
@@ -0,0 +1,119 @@
+import { CommonModule } from "@angular/common";
+import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
+import { ProgressModule } from "@bitwarden/components";
+
+export interface PasswordColorText {
+ color: BackgroundTypes;
+ text: string;
+}
+export type PasswordStrengthScore = 0 | 1 | 2 | 3 | 4;
+
+type SizeTypes = "small" | "default" | "large";
+type BackgroundTypes = "danger" | "primary" | "success" | "warning";
+
+@Component({
+ selector: "tools-password-strength",
+ templateUrl: "password-strength-v2.component.html",
+ standalone: true,
+ imports: [CommonModule, JslibModule, ProgressModule],
+})
+export class PasswordStrengthV2Component implements OnChanges {
+ /**
+ * The size (height) of the password strength component.
+ * Possible values are "default", "small" and "large".
+ */
+ @Input() size: SizeTypes = "default";
+ /**
+ * Determines whether to show the password strength score text on the progress bar or not.
+ */
+ @Input() showText = false;
+ /**
+ * Optional email address which can be used as input for the password strength calculation
+ */
+ @Input() email: string;
+ /**
+ * Optional name which can be used as input for the password strength calculation
+ */
+ @Input() name: string;
+ /**
+ * Sets the password value and updates the password strength.
+ *
+ * @param value - password provided by the hosting component
+ */
+ @Input() set password(value: string) {
+ this.updatePasswordStrength(value);
+ }
+ /**
+ * Emits the password strength score.
+ *
+ * @remarks
+ * The password strength score represents the strength of a password.
+ * It is emitted as an event when the password strength changes.
+ */
+ @Output() passwordStrengthScore = new EventEmitter();
+
+ /**
+ * Emits an event with the password score text and color.
+ */
+ @Output() passwordScoreTextWithColor = new EventEmitter();
+
+ passwordScore: PasswordStrengthScore;
+ scoreWidth = 0;
+ color: BackgroundTypes = "danger";
+ text: string;
+
+ private passwordStrengthTimeout: number | NodeJS.Timeout;
+
+ constructor(
+ private i18nService: I18nService,
+ private passwordStrengthService: PasswordStrengthServiceAbstraction,
+ ) {}
+
+ ngOnChanges(): void {
+ this.passwordStrengthTimeout = setTimeout(() => {
+ this.scoreWidth = this.passwordScore == null ? 0 : (this.passwordScore + 1) * 20;
+
+ switch (this.passwordScore) {
+ case 4:
+ this.color = "success";
+ this.text = this.i18nService.t("strong");
+ break;
+ case 3:
+ this.color = "primary";
+ this.text = this.i18nService.t("good");
+ break;
+ case 2:
+ this.color = "warning";
+ this.text = this.i18nService.t("weak");
+ break;
+ default:
+ this.color = "danger";
+ this.text = this.passwordScore != null ? this.i18nService.t("weak") : null;
+ break;
+ }
+
+ this.passwordScoreTextWithColor.emit({
+ color: this.color,
+ text: this.text,
+ } as PasswordColorText);
+ }, 300);
+ }
+
+ updatePasswordStrength(password: string) {
+ if (this.passwordStrengthTimeout != null) {
+ clearTimeout(this.passwordStrengthTimeout);
+ }
+
+ const strengthResult = this.passwordStrengthService.getPasswordStrength(
+ password,
+ this.email,
+ this.name?.trim().toLowerCase().split(" "),
+ );
+ this.passwordScore = strengthResult == null ? null : strengthResult.score;
+ this.passwordStrengthScore.emit(this.passwordScore);
+ }
+}
diff --git a/libs/angular/src/tools/password-strength/password-strength.component.ts b/libs/angular/src/tools/password-strength/password-strength.component.ts
index 305feefbf2..5960bdb9d0 100644
--- a/libs/angular/src/tools/password-strength/password-strength.component.ts
+++ b/libs/angular/src/tools/password-strength/password-strength.component.ts
@@ -8,6 +8,9 @@ export interface PasswordColorText {
text: string;
}
+/**
+ * @deprecated July 2024: Use new PasswordStrengthV2Component instead
+ */
@Component({
selector: "app-password-strength",
templateUrl: "password-strength.component.html",
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
index 8abc0c7755..07606add8b 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
@@ -76,7 +76,8 @@
>
{{ "exportPasswordDescription" | i18n }}
-
+
+
{{ "confirmFilePassword" | i18n }}
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
index baa463d913..84d4c45934 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
@@ -12,7 +12,7 @@ import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/fo
import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
-import { PasswordStrengthComponent } from "@bitwarden/angular/tools/password-strength/password-strength.component";
+import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -58,6 +58,7 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component";
RadioButtonModule,
ExportScopeCalloutComponent,
UserVerificationDialogComponent,
+ PasswordStrengthV2Component,
],
})
export class ExportComponent implements OnInit, OnDestroy {
@@ -110,7 +111,7 @@ export class ExportComponent implements OnInit, OnDestroy {
@Output()
onSuccessfulExport = new EventEmitter();
- @ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent;
+ @ViewChild(PasswordStrengthV2Component) passwordStrengthComponent: PasswordStrengthV2Component;
encryptedExportType = EncryptedExportType;
protected showFilePassword: boolean;