diff --git a/apps/web/src/app/settings/change-password.component.html b/apps/web/src/app/settings/change-password.component.html index e74881db02..3321b8f9df 100644 --- a/apps/web/src/app/settings/change-password.component.html +++ b/apps/web/src/app/settings/change-password.component.html @@ -41,6 +41,10 @@ appInputVerbatim autocomplete="new-password" /> + + {{ "important" | i18n }} + {{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }} +
- - +
+ + +
@@ -100,6 +107,17 @@
+
+ + +
diff --git a/apps/web/src/app/settings/change-password.component.ts b/apps/web/src/app/settings/change-password.component.ts index a7d2930c35..fa57b9f4ae 100644 --- a/apps/web/src/app/settings/change-password.component.ts +++ b/apps/web/src/app/settings/change-password.component.ts @@ -4,6 +4,7 @@ import { firstValueFrom } from "rxjs"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; @@ -39,6 +40,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent { rotateEncKey = false; currentMasterPassword: string; masterPasswordHint: string; + checkForBreaches = true; + characterMinimumMessage = ""; constructor( i18nService: I18nService, @@ -48,6 +51,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent { passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, policyService: PolicyService, + private auditService: AuditService, private folderService: FolderService, private cipherService: CipherService, private syncService: SyncService, @@ -77,6 +81,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent { this.masterPasswordHint = (await this.apiService.getProfile()).masterPasswordHint; await super.ngOnInit(); + + this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength); } async rotateEncKeyClicked() { @@ -133,6 +139,20 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent { return; } + if (this.masterPasswordHint != null && this.masterPasswordHint == this.masterPassword) { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorOccurred"), + this.i18nService.t("hintEqualsPassword") + ); + return; + } + + this.leakedPassword = false; + if (this.checkForBreaches) { + this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0; + } + await super.submit(); } diff --git a/apps/web/src/auth/register-form/register-form.component.html b/apps/web/src/auth/register-form/register-form.component.html index ad09129568..f7c24f6a96 100644 --- a/apps/web/src/auth/register-form/register-form.component.html +++ b/apps/web/src/auth/register-form/register-form.component.html @@ -48,7 +48,7 @@ > {{ "important" | i18n }} - {{ "masterPassImportant" | i18n }} + {{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }} - {{ "masterPassHint" | i18n }} + {{ "masterPassHintLabel" | i18n }} {{ "masterPassHintDesc" | i18n }} @@ -91,7 +91,20 @@
- +
+
+ + +
+
0) { + this.characterMinimumMessage = ""; + } else { + this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength); + } } async submit() { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index de2f1119f2..6562e4f5aa 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -659,7 +659,7 @@ "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." }, "masterPassImportant": { - "message": "Master passwords cannot be recovered if you forget it!" + "message": "Your master password cannot be recovered if you forget it!" }, "masterPassHintDesc": { "message": "A master password hint can help you remember your password if you forget it." @@ -3533,7 +3533,7 @@ "message": "Weak master password" }, "weakMasterPasswordDesc": { - "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + "message": "Weak password identified. Use a strong password to protect your account. Are you sure you want to use a weak password?" }, "rotateAccountEncKey": { "message": "Also rotate my account's encryption key" @@ -6481,5 +6481,41 @@ }, "secretsManagerEnable": { "message": "Enable Secrets Manager Beta" + }, + "checkForBreaches": { + "message": "Check known data breaches for this password" + }, + "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?" + }, + "important": { + "message": "Important:" + }, + "characterMinimum": { + "message": "$LENGTH$ character minimum", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "masterPasswordMinimumlength": { + "message": "Master password must be at least $LENGTH$ characters long.", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } } } diff --git a/libs/angular/src/auth/components/change-password.component.ts b/libs/angular/src/auth/components/change-password.component.ts index 3421b509e1..989b18f90f 100644 --- a/libs/angular/src/auth/components/change-password.component.ts +++ b/libs/angular/src/auth/components/change-password.component.ts @@ -26,6 +26,8 @@ export class ChangePasswordComponent implements OnInit, OnDestroy { passwordStrengthResult: any; color: string; text: string; + leakedPassword: boolean; + minimumLength = Utils.minimumPasswordLength; protected email: string; protected kdf: KdfType; @@ -117,11 +119,11 @@ export class ChangePasswordComponent implements OnInit, OnDestroy { ); return false; } - if (this.masterPassword.length < 8) { + if (this.masterPassword.length < this.minimumLength) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), - this.i18nService.t("masterPasswordMinlength", Utils.minimumPasswordLength) + this.i18nService.t("masterPasswordMinimumlength", this.minimumLength) ); return false; } @@ -152,7 +154,21 @@ export class ChangePasswordComponent implements OnInit, OnDestroy { return false; } - if (strengthResult != null && strengthResult.score < 3) { + const weakPassword = strengthResult != null && strengthResult.score < 3; + + if (weakPassword && this.leakedPassword) { + 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 false; + } + } + if (weakPassword) { const result = await this.platformUtilsService.showDialog( this.i18nService.t("weakMasterPasswordDesc"), this.i18nService.t("weakMasterPassword"), @@ -164,7 +180,18 @@ export class ChangePasswordComponent implements OnInit, OnDestroy { return false; } } - + if (this.leakedPassword) { + 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 false; + } + } return true; }