[SG-458] Master Password security checks (browser) (#4502)

* work: add base logic for password lookup

* work: added browser support for hibp

* work: SG-558 combine weak + leak warning

* fix: language stuff

* fix: form values are neater tho :(

* fix: no cast
This commit is contained in:
Brandon Maharaj 2023-01-24 16:04:01 -05:00 committed by GitHub
parent ae3edcc34d
commit 497b08df44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 11 deletions

View File

@ -2046,5 +2046,35 @@
},
"rememberEmail": {
"message": "Remember email"
},
"exposedMasterPassword": {
"message": "Exposed Master Password"
},
"exposedMasterPasswordDesc": {
"message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?"
},
"weakAndExposedMasterPassword": {
"message": "Weak and Exposed Master Password"
},
"weakAndBreachedMasterPasswordDesc": {
"message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?"
},
"checkForBreaches": {
"message": "Check known data breaches for this password"
},
"important": {
"message": "Important:"
},
"masterPasswordHint": {
"message": "Your master password cannot be recovered if you forget it!"
},
"characterMinimum": {
"message": "$LENGTH$ character minimum",
"placeholders": {
"length": {
"content": "$1",
"example": "14"
}
}
}
}

View File

@ -66,7 +66,8 @@
</div>
</div>
<div id="masterPasswordHelp" class="box-footer">
{{ "masterPassDesc" | i18n }}
<b>{{ "important" | i18n }}</b> {{ "masterPasswordHint" | i18n }}
{{ characterMinimumMessage }}
</div>
</div>
<div class="box">
@ -107,6 +108,17 @@
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
<div class="box-content row-top-padding">
<div
class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
appBoxRow
>
<input type="checkbox" id="checkForBreaches" formControlName="checkForBreaches" />
<label for="checkForBreaches">
{{ "checkForBreaches" | i18n }}
</label>
</div>
</div>
</div>
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<div class="box last" *ngIf="showTerms">

View File

@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
@ -34,7 +35,8 @@ export class RegisterComponent extends BaseRegisterComponent {
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
logService: LogService
logService: LogService,
auditService: AuditService
) {
super(
formValidationErrorService,
@ -48,7 +50,8 @@ export class RegisterComponent extends BaseRegisterComponent {
platformUtilsService,
passwordGenerationService,
environmentService,
logService
logService,
auditService
);
}
}

View File

@ -83,6 +83,10 @@
padding-bottom: 10px;
margin: 5px;
}
&.row-top-padding {
padding-top: 10px;
}
}
.box-footer {

View File

@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@ -36,7 +37,8 @@ export class RegisterComponent extends BaseRegisterComponent implements OnInit,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService
logService: LogService,
auditService: AuditService
) {
super(
formValidationErrorService,
@ -50,7 +52,8 @@ export class RegisterComponent extends BaseRegisterComponent implements OnInit,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
logService,
auditService
);
}

View File

@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
@ -41,7 +42,8 @@ export class RegisterFormComponent extends BaseRegisterComponent {
passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService
logService: LogService,
auditService: AuditService
) {
super(
formValidationErrorService,
@ -55,7 +57,8 @@ export class RegisterFormComponent extends BaseRegisterComponent {
platformUtilsService,
passwordGenerationService,
environmentService,
logService
logService,
auditService
);
}

View File

@ -3,6 +3,7 @@ import { AbstractControl, UntypedFormBuilder, ValidatorFn, Validators } from "@a
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
@ -38,6 +39,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
showTerms = true;
showErrorSummary = false;
passwordStrengthResult: any;
characterMinimumMessage: string;
minimumLength = 8;
color: string;
text: string;
@ -45,8 +48,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
{
email: ["", [Validators.required, Validators.email]],
name: [""],
masterPassword: ["", [Validators.required, Validators.minLength(8)]],
confirmMasterPassword: ["", [Validators.required, Validators.minLength(8)]],
masterPassword: ["", [Validators.required, Validators.minLength(this.minimumLength)]],
confirmMasterPassword: ["", [Validators.required, Validators.minLength(this.minimumLength)]],
hint: [
null,
[
@ -56,6 +59,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
),
],
],
checkForBreaches: [false],
acceptPolicies: [false, [this.acceptPoliciesValidation()]],
},
{
@ -85,10 +89,12 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
protected logService: LogService
protected logService: LogService,
protected auditService: AuditService
) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
async ngOnInit() {
@ -212,7 +218,24 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
return { isValid: false };
}
if (this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3) {
const passwordWeak =
this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3;
const passwordLeak =
this.formGroup.controls.checkForBreaches.value &&
(await this.auditService.passwordLeaked(this.formGroup.controls.masterPassword.value)) > 0;
if (passwordWeak && passwordLeak) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakAndBreachedMasterPasswordDesc"),
this.i18nService.t("weakAndExposedMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return { isValid: false };
}
} else if (passwordWeak) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
@ -223,7 +246,19 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
if (!result) {
return { isValid: false };
}
} else if (passwordLeak) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("exposedMasterPasswordDesc"),
this.i18nService.t("exposedMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return { isValid: false };
}
}
return { isValid: true };
}