diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index fea9c04ca6..41a8173487 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -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"
+ }
+ }
}
}
diff --git a/apps/browser/src/popup/accounts/register.component.html b/apps/browser/src/popup/accounts/register.component.html
index 6f7e543ea1..6386571e60 100644
--- a/apps/browser/src/popup/accounts/register.component.html
+++ b/apps/browser/src/popup/accounts/register.component.html
@@ -66,7 +66,8 @@
@@ -107,6 +108,17 @@
+
+
+
+
+ {{ "checkForBreaches" | i18n }}
+
+
+
diff --git a/apps/browser/src/popup/accounts/register.component.ts b/apps/browser/src/popup/accounts/register.component.ts
index 6ee4719457..d199b48485 100644
--- a/apps/browser/src/popup/accounts/register.component.ts
+++ b/apps/browser/src/popup/accounts/register.component.ts
@@ -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
);
}
}
diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss
index a14427357f..ef13a7f545 100644
--- a/apps/browser/src/popup/scss/box.scss
+++ b/apps/browser/src/popup/scss/box.scss
@@ -83,6 +83,10 @@
padding-bottom: 10px;
margin: 5px;
}
+
+ &.row-top-padding {
+ padding-top: 10px;
+ }
}
.box-footer {
diff --git a/apps/desktop/src/app/accounts/register.component.ts b/apps/desktop/src/app/accounts/register.component.ts
index 2cfc1cd8ad..94706064d2 100644
--- a/apps/desktop/src/app/accounts/register.component.ts
+++ b/apps/desktop/src/app/accounts/register.component.ts
@@ -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
);
}
diff --git a/apps/web/src/app/accounts/register-form/register-form.component.ts b/apps/web/src/app/accounts/register-form/register-form.component.ts
index 6cd62ea13e..242ce39baf 100644
--- a/apps/web/src/app/accounts/register-form/register-form.component.ts
+++ b/apps/web/src/app/accounts/register-form/register-form.component.ts
@@ -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
);
}
diff --git a/libs/angular/src/components/register.component.ts b/libs/angular/src/components/register.component.ts
index 029639e7eb..c370bea8b3 100644
--- a/libs/angular/src/components/register.component.ts
+++ b/libs/angular/src/components/register.component.ts
@@ -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 };
}