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
This commit is contained in:
Carlos Gonçalves 2023-02-23 15:15:45 +00:00 committed by GitHub
parent 80c2f20f58
commit 30a66a9f65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 20 deletions

View File

@ -41,6 +41,10 @@
appInputVerbatim
autocomplete="new-password"
/>
<bit-hint>
<span class="tw-font-semibold">{{ "important" | i18n }}</span>
{{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }}
</bit-hint>
<app-password-strength
[password]="masterPassword"
[email]="email"
@ -67,15 +71,18 @@
</div>
</div>
<div class="form-group">
<label for="masterPasswordHint">{{ "masterPassHintLabel" | i18n }}</label>
<input
id="masterPasswordHint"
class="form-control"
maxlength="50"
type="text"
name="MasterPasswordHint"
[(ngModel)]="masterPasswordHint"
/>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="checkForBreaches"
name="checkForBreaches"
[(ngModel)]="checkForBreaches"
/>
<label class="form-check-label" for="checkForBreaches">
{{ "checkForBreaches" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
@ -100,6 +107,17 @@
</a>
</div>
</div>
<div class="form-group">
<label for="masterPasswordHint">{{ "masterPassHintLabel" | i18n }}</label>
<input
id="masterPasswordHint"
class="form-control"
maxlength="50"
type="text"
name="MasterPasswordHint"
[(ngModel)]="masterPasswordHint"
/>
</div>
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
{{ "changeMasterPassword" | i18n }}
</button>

View File

@ -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();
}

View File

@ -48,7 +48,7 @@
></button>
<bit-hint>
<span class="tw-font-semibold">{{ "important" | i18n }}</span>
{{ "masterPassImportant" | i18n }}
{{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }}
</bit-hint>
</bit-form-field>
<app-password-strength
@ -82,7 +82,7 @@
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "masterPassHint" | i18n }}</bit-label>
<bit-label>{{ "masterPassHintLabel" | i18n }}</bit-label>
<input id="register-form_input_hint" bitInput type="text" formControlName="hint" />
<bit-hint>{{ "masterPassHintDesc" | i18n }}</bit-hint>
</bit-form-field>
@ -91,7 +91,20 @@
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="checkForBreaches"
name="CheckBreach"
formControlName="checkForBreaches"
/>
<label class="form-check-label" for="checkForBreaches">
{{ "checkForBreaches" | i18n }}
</label>
</div>
</div>
<div class="tw-mb-3 tw-flex tw-items-start" *ngIf="showTerms">
<div class="tw-flex tw-h-6 tw-items-center">
<input

View File

@ -28,6 +28,7 @@ export class RegisterFormComponent extends BaseRegisterComponent {
@Input() referenceDataValue: ReferenceEventRequest;
showErrorSummary = false;
characterMinimumMessage: string;
constructor(
formValidationErrorService: FormValidationErrorsService,
@ -63,14 +64,18 @@ export class RegisterFormComponent extends BaseRegisterComponent {
}
async ngOnInit() {
// TODO: Remove once breach checks are added for web
this.formGroup.patchValue({ checkForBreaches: false });
await super.ngOnInit();
this.referenceData = this.referenceDataValue;
if (this.queryParamEmail) {
this.formGroup.get("email")?.setValue(this.queryParamEmail);
}
if (this.enforcedPolicyOptions != null && this.enforcedPolicyOptions.minLength > 0) {
this.characterMinimumMessage = "";
} else {
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
}
async submit() {

View File

@ -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"
}
}
}
}

View File

@ -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;
}