From 30a66a9f656458e9117aa046be064cf13881cfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gon=C3=A7alves?= Date: Thu, 23 Feb 2023 15:15:45 +0000 Subject: [PATCH] Master password security checks - web (#4799) * [SG-571][SG-572][SG-573][SG-574] Master password change (web vault) (#4635) * SG-571 Add option to check master password breach * SG-571 Fix lint errors * SG-572 SG-573 SG-574 Add logic for leaked password * SG-571 Show error when new password equals hint * SG-571 Minor changes * SG-571 Undo changes * [SG-457][SG-553][SG-554][SG-555][SG-761] Master password security update - account creation (web) (#4672) * SG-571 Add option to check master password breach * SG-571 Fix lint errors * SG-572 SG-573 SG-574 Add logic for leaked password * SG-571 Show error when new password equals hint * SG-571 Minor changes * SG-761 Improve copy on master password * SG-571 Undo changes * SG-457 Add option to check for password leak * SG-457 Updated master password hint copy * SG-457 Hide minimum char message when joining org * SG-457 Added missing changes from last commit * SG-457 Fixed minimum length * SG-457 Updated message with dynamic minimum length * SG-457 Set checkForBreaches to true by default --- .../settings/change-password.component.html | 36 ++++++++++++----- .../app/settings/change-password.component.ts | 20 ++++++++++ .../register-form.component.html | 19 +++++++-- .../register-form/register-form.component.ts | 9 ++++- apps/web/src/locales/en/messages.json | 40 ++++++++++++++++++- .../components/change-password.component.ts | 35 ++++++++++++++-- 6 files changed, 139 insertions(+), 20 deletions(-) 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; }