1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

[SG-414] Refactor password strength component (#3186)

* moved password strength to libs

* refactored password strength component

* made changes on desktop and browser to reuse component

* resolved suggestions from PR review

* shared module restructure

* shared module restructure
This commit is contained in:
Gbubemi Smith
2022-08-01 23:26:50 +01:00
committed by GitHub
parent 8820a42ec9
commit 257fb0c0af
27 changed files with 259 additions and 425 deletions

View File

@@ -12,20 +12,22 @@ import { EncString } from "@bitwarden/common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
@Directive()
export class ChangePasswordComponent implements OnInit {
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
masterPasswordScore: number;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
passwordStrengthResult: any;
color: string;
text: string;
protected email: string;
protected kdf: KdfType;
protected kdfIterations: number;
private masterPasswordStrengthTimeout: any;
constructor(
protected i18nService: I18nService,
protected cryptoService: CryptoService,
@@ -116,10 +118,7 @@ export class ChangePasswordComponent implements OnInit {
return false;
}
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
const strengthResult = this.passwordStrengthResult;
if (
this.enforcedPolicyOptions != null &&
@@ -153,19 +152,6 @@ export class ChangePasswordComponent implements OnInit {
return true;
}
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
@@ -178,18 +164,12 @@ export class ChangePasswordComponent implements OnInit {
}
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
return userInput;
getStrengthResult(result: any) {
this.passwordStrengthResult = result;
}
getPasswordScoreText(event: PasswordColorText) {
this.color = event.color;
this.text = event.text;
}
}

View File

@@ -22,6 +22,8 @@ import { KeysRequest } from "@bitwarden/common/models/request/keysRequest";
import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenceEventRequest";
import { RegisterRequest } from "@bitwarden/common/models/request/registerRequest";
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
@@ -31,10 +33,12 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
showPassword = false;
formPromise: Promise<any>;
masterPasswordScore: number;
referenceData: ReferenceEventRequest;
showTerms = true;
showErrorSummary = false;
passwordStrengthResult: any;
color: string;
text: string;
formGroup = this.formBuilder.group(
{
@@ -63,7 +67,6 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
);
protected successRoute = "login";
private masterPasswordStrengthTimeout: any;
constructor(
protected formValidationErrorService: FormValidationErrorsService,
@@ -87,36 +90,6 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
this.setupCaptcha();
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async submit(showToast = true) {
let email = this.formGroup.get("email")?.value;
let name = this.formGroup.get("name")?.value;
@@ -147,11 +120,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
return;
}
const strengthResult = this.passwordGenerationService.passwordStrength(
masterPassword,
this.getPasswordStrengthUserInput()
);
if (strengthResult != null && strengthResult.score < 3) {
if (this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
@@ -235,39 +204,13 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
this.showPassword = !this.showPassword;
}
updatePasswordStrength() {
const masterPassword = this.formGroup.get("masterPassword")?.value;
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
getStrengthResult(result: any) {
this.passwordStrengthResult = result;
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const email = this.formGroup.get("email")?.value;
const name = this.formGroup.get("name").value;
const atPosition = email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
if (name != null && name !== "") {
userInput = userInput.concat(name.trim().toLowerCase().split(" "));
}
return userInput;
getPasswordScoreText(event: PasswordColorText) {
this.color = event.color;
this.text = event.text;
}
private getErrorToastMessage() {

View File

@@ -29,6 +29,7 @@ import { I18nPipe } from "./pipes/i18n.pipe";
import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
import { SearchPipe } from "./pipes/search.pipe";
import { UserNamePipe } from "./pipes/user-name.pipe";
import { PasswordStrengthComponent } from "./shared/components/password-strength/password-strength.component";
@NgModule({
imports: [
@@ -68,6 +69,7 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
StopPropDirective,
TrueFalseValueDirective,
UserNamePipe,
PasswordStrengthComponent,
],
exports: [
A11yInvalidDirective,
@@ -97,6 +99,7 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
StopPropDirective,
TrueFalseValueDirective,
UserNamePipe,
PasswordStrengthComponent,
],
providers: [CreditCardNumberPipe, DatePipe, I18nPipe, SearchPipe, UserNamePipe],
})

View File

@@ -0,0 +1,14 @@
<div class="progress">
<div
class="progress-bar {{ color }}"
role="progressbar"
[ngStyle]="{ width: scoreWidth + '%' }"
attr.aria-valuenow="{{ scoreWidth }}"
aria-valuemin="0"
aria-valuemax="100"
>
<ng-container *ngIf="showText && text">
{{ text }}
</ng-container>
</div>
</div>

View File

@@ -0,0 +1,133 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
export interface PasswordColorText {
color: string;
text: string;
}
@Component({
selector: "app-password-strength",
templateUrl: "password-strength.component.html",
})
export class PasswordStrengthComponent implements OnChanges {
@Input() showText = false;
@Input() email: string;
@Input() password: string;
@Input() name: string;
@Output() passwordStrengthResult = new EventEmitter<any>();
@Output() passwordScoreColor = new EventEmitter<PasswordColorText>();
masterPasswordScore: number;
scoreWidth = 0;
color = "bg-danger";
text: string;
private masterPasswordStrengthTimeout: any;
//used by desktop and browser to display strength text color
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
//used by desktop and browser to display strength text
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
constructor(
private i18nService: I18nService,
private passwordGenerationService: PasswordGenerationService
) {}
ngOnChanges(changes: SimpleChanges): void {
this.masterPasswordStrengthTimeout = setTimeout(() => {
this.updatePasswordStrength(changes.password?.currentValue);
this.scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
this.color = "bg-success";
this.text = this.i18nService.t("strong");
break;
case 3:
this.color = "bg-primary";
this.text = this.i18nService.t("good");
break;
case 2:
this.color = "bg-warning";
this.text = this.i18nService.t("weak");
break;
default:
this.color = "bg-danger";
this.text = this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
break;
}
this.setPasswordScoreText(this.color, this.text);
}, 100);
}
updatePasswordStrength(password: string) {
const masterPassword = password;
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
const strengthResult = this.passwordGenerationService.passwordStrength(
masterPassword,
this.getPasswordStrengthUserInput()
);
this.passwordStrengthResult.emit(strengthResult);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}
getPasswordStrengthUserInput() {
let userInput: string[] = [];
const email = this.email;
const name = this.name;
const atPosition = email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
if (name != null && name !== "") {
userInput = userInput.concat(name.trim().toLowerCase().split(" "));
}
return userInput;
}
setPasswordScoreText(color: string, text: string) {
color = color.slice(3);
this.passwordScoreColor.emit({ color: color, text: text });
}
}