mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-29 12:55:21 +01:00
[PM-5165][PM-8645] Migrate password strength component (#9912)
* Create standalone password-strength-v2 component * Add deprecation notice to old component * PM-8645: Use new password-strength component on export * Remove unneccessary variable * Remove setPasswordScoreText method * Rename passwordStrengthResult to passwordStrengthScore and assign proper type * Add missing types * Document component Inputs/Outputs * Add unit tests --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
parent
f446c3b454
commit
33de685b40
@ -0,0 +1,7 @@
|
|||||||
|
<bit-progress
|
||||||
|
[size]="size"
|
||||||
|
[text]="text"
|
||||||
|
[bgColor]="color"
|
||||||
|
[showText]="showText"
|
||||||
|
[barWidth]="scoreWidth"
|
||||||
|
></bit-progress>
|
@ -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<PasswordStrengthV2Component>;
|
||||||
|
|
||||||
|
const mockPasswordStrengthService = mock<PasswordStrengthServiceAbstraction>();
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
@ -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<PasswordStrengthScore>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event with the password score text and color.
|
||||||
|
*/
|
||||||
|
@Output() passwordScoreTextWithColor = new EventEmitter<PasswordColorText>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,9 @@ export interface PasswordColorText {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated July 2024: Use new PasswordStrengthV2Component instead
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-password-strength",
|
selector: "app-password-strength",
|
||||||
templateUrl: "password-strength.component.html",
|
templateUrl: "password-strength.component.html",
|
||||||
|
@ -76,7 +76,8 @@
|
|||||||
></button>
|
></button>
|
||||||
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
|
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
<app-password-strength [password]="filePassword" [showText]="true"> </app-password-strength>
|
<tools-password-strength [password]="filePassword" [showText]="true">
|
||||||
|
</tools-password-strength>
|
||||||
</div>
|
</div>
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
|
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
|
||||||
|
@ -12,7 +12,7 @@ import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/fo
|
|||||||
import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs";
|
import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
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 { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@ -58,6 +58,7 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component";
|
|||||||
RadioButtonModule,
|
RadioButtonModule,
|
||||||
ExportScopeCalloutComponent,
|
ExportScopeCalloutComponent,
|
||||||
UserVerificationDialogComponent,
|
UserVerificationDialogComponent,
|
||||||
|
PasswordStrengthV2Component,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ExportComponent implements OnInit, OnDestroy {
|
export class ExportComponent implements OnInit, OnDestroy {
|
||||||
@ -110,7 +111,7 @@ export class ExportComponent implements OnInit, OnDestroy {
|
|||||||
@Output()
|
@Output()
|
||||||
onSuccessfulExport = new EventEmitter<string>();
|
onSuccessfulExport = new EventEmitter<string>();
|
||||||
|
|
||||||
@ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent;
|
@ViewChild(PasswordStrengthV2Component) passwordStrengthComponent: PasswordStrengthV2Component;
|
||||||
|
|
||||||
encryptedExportType = EncryptedExportType;
|
encryptedExportType = EncryptedExportType;
|
||||||
protected showFilePassword: boolean;
|
protected showFilePassword: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user