mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-30 13:03:53 +01:00
Enforce Master Password Policies (Change/Register) (#478)
* Initial commit for change password mp policy enforcement * Initial commit of mp policy for registering * Testing Register component * Final testing complete * Reverting service module URLs * Requested changes and build fix * Updated submit function
This commit is contained in:
parent
cd9b1b906c
commit
e7e5816ded
@ -21,6 +21,25 @@
|
||||
<small class="form-text text-muted">{{'yourNameDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<app-callout type="info" *ngIf="enforcedPolicyOptions">
|
||||
<p>{{'masterPasswordPolicyInEffect' | i18n}}</p>
|
||||
<ul>
|
||||
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
||||
{{'policyInEffectMinComplexity' | i18n : enforcedPolicyOptions?.minComplexity.toString()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
||||
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireUpper">
|
||||
{{'policyInEffectUppercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireLower">
|
||||
{{'policyInEffectLowercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
|
||||
{{'policyInEffectNumbers' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
|
||||
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||
<div class="d-flex">
|
||||
<div class="w-100">
|
||||
|
@ -10,10 +10,12 @@ import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/components/register.component';
|
||||
|
||||
import { MasterPasswordPolicyOptions } from 'jslib/models/domain/masterPasswordPolicyOptions';
|
||||
import { Policy } from 'jslib/models/domain/policy';
|
||||
|
||||
import { PolicyData } from 'jslib/models/data/policyData';
|
||||
@ -27,12 +29,13 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
showTerms = true;
|
||||
|
||||
private policies: Policy[];
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
constructor(authService: AuthService, router: Router,
|
||||
i18nService: I18nService, cryptoService: CryptoService,
|
||||
apiService: ApiService, private route: ActivatedRoute,
|
||||
stateService: StateService, platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService) {
|
||||
passwordGenerationService: PasswordGenerationService, private policyService: PolicyService) {
|
||||
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
|
||||
passwordGenerationService);
|
||||
this.showTerms = !platformUtilsService.isSelfHost();
|
||||
@ -65,5 +68,21 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (this.policies != null) {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(this.policies);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(this.masterPasswordScore, this.masterPassword,
|
||||
this.enforcedPolicyOptions)) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit();
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<label for="masterPassMinLength">{{'minLength' | i18n}}</label>
|
||||
<input id="masterPassMinLength" class="form-control" type="number"
|
||||
<input id="masterPassMinLength" class="form-control" type="number" min="8"
|
||||
name="MasterPassMinLength" [(ngModel)]="masterPassMinLength">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,20 @@
|
||||
<app-callout type="warning">{{'loggedOutWarning' | i18n}}</app-callout>
|
||||
<app-callout type="info" *ngIf="enforcedPolicyOptions">
|
||||
<p>{{'masterPasswordPolicyInEffect' | i18n}}</p>
|
||||
<ul>
|
||||
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
||||
{{'policyInEffectMinComplexity' | i18n : enforcedPolicyOptions?.minComplexity.toString()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
||||
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireUpper">{{'policyInEffectUppercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireLower">{{'policyInEffectLowercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireNumbers">{{'policyInEffectNumbers' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireSpecial">{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
|
@ -14,10 +14,12 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { CipherString } from 'jslib/models/domain/cipherString';
|
||||
import { MasterPasswordPolicyOptions } from 'jslib/models/domain/masterPasswordPolicyOptions';
|
||||
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
||||
|
||||
import { CipherWithIdRequest } from 'jslib/models/request/cipherWithIdRequest';
|
||||
@ -36,6 +38,7 @@ export class ChangePasswordComponent implements OnInit {
|
||||
formPromise: Promise<any>;
|
||||
masterPasswordScore: number;
|
||||
rotateEncKey = false;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
private masterPasswordStrengthTimeout: any;
|
||||
private email: string;
|
||||
@ -45,10 +48,12 @@ export class ChangePasswordComponent implements OnInit {
|
||||
private cryptoService: CryptoService, private messagingService: MessagingService,
|
||||
private userService: UserService, private passwordGenerationService: PasswordGenerationService,
|
||||
private platformUtilsService: PlatformUtilsService, private folderService: FolderService,
|
||||
private cipherService: CipherService, private syncService: SyncService) { }
|
||||
private cipherService: CipherService, private syncService: SyncService,
|
||||
private policyService: PolicyService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.email = await this.userService.getEmail();
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@ -77,6 +82,17 @@ export class ChangePasswordComponent implements OnInit {
|
||||
|
||||
const strengthResult = this.passwordGenerationService.passwordStrength(this.newMasterPassword,
|
||||
this.getPasswordStrengthUserInput());
|
||||
|
||||
if (this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
strengthResult.score,
|
||||
this.newMasterPassword,
|
||||
this.enforcedPolicyOptions)) {
|
||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strengthResult != null && strengthResult.score < 3) {
|
||||
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
|
||||
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
|
||||
|
@ -2989,5 +2989,47 @@
|
||||
},
|
||||
"passwordGeneratorPolicyInEffect": {
|
||||
"message": "One or more organization policies are affecting your generator settings."
|
||||
},
|
||||
"masterPasswordPolicyInEffect": {
|
||||
"message": "One or more organization policies require your master password to meet the following requirements:"
|
||||
},
|
||||
"policyInEffectMinComplexity": {
|
||||
"message": "Minimum complexity score of $SCORE$",
|
||||
"placeholders": {
|
||||
"score": {
|
||||
"content": "$1",
|
||||
"example": "4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"policyInEffectMinLength": {
|
||||
"message": "Minimum length of $LENGTH$",
|
||||
"placeholders": {
|
||||
"length": {
|
||||
"content": "$1",
|
||||
"example": "14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"policyInEffectUppercase": {
|
||||
"message": "Contain one or more uppercase characters"
|
||||
},
|
||||
"policyInEffectLowercase": {
|
||||
"message": "Contain one or more lowercase characters"
|
||||
},
|
||||
"policyInEffectNumbers": {
|
||||
"message": "Contain one or more numbers"
|
||||
},
|
||||
"policyInEffectSpecial": {
|
||||
"message": "Contain one or more of the following special characters $CHARS$",
|
||||
"placeholders": {
|
||||
"chars": {
|
||||
"content": "$1",
|
||||
"example": "!@#$%^&*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"masterPasswordPolicyRequirementsNotMet": {
|
||||
"message": "Your new master password does not meet the policy requirements."
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user