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:
Vincent Salucci 2020-03-03 10:20:28 -06:00 committed by GitHub
parent cd9b1b906c
commit e7e5816ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 4 deletions

View File

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

View File

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

View File

@ -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>
@ -110,4 +110,4 @@
</div>
</form>
</div>
</div>
</div>

View File

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

View File

@ -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'),

View File

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