Browse Source

UI: Add max password length with validation

pull/9791/head
Artem Dzhereleiko 3 years ago
parent
commit
654c887f0e
  1. 59
      ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html
  2. 27
      ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts
  3. 7
      ui-ngx/src/app/modules/home/pages/security/security.component.html
  4. 5
      ui-ngx/src/app/modules/home/pages/security/security.component.ts
  5. 2
      ui-ngx/src/app/shared/models/settings.models.ts
  6. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json

59
ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html

@ -51,24 +51,36 @@
<fieldset class="fields-group">
<legend class="group-title" translate>admin.password-policy</legend>
<section formGroupName="passwordPolicy">
<mat-form-field class="mat-block">
<mat-label translate>admin.minimum-password-length</mat-label>
<input matInput type="number"
formControlName="minimumLength"
step="1"
min="5"
max="50"
required/>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLength').hasError('required')">
{{ 'admin.minimum-password-length-required' | translate }}
</mat-error>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLength').hasError('min')">
{{ 'admin.minimum-password-length-range' | translate }}
</mat-error>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLength').hasError('max')">
{{ 'admin.minimum-password-length-range' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.minimum-password-length</mat-label>
<input matInput type="number"
formControlName="minimumLength"
step="1"
min="6"
max="50"
required/>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLength').hasError('required')">
{{ 'admin.minimum-password-length-required' | translate }}
</mat-error>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLength').hasError('min')">
{{ 'admin.minimum-password-length-range' | translate }}
</mat-error>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLength').hasError('max')">
{{ 'admin.minimum-password-length-range' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" subscriptSizing="dynamic">
<mat-label translate>admin.maximum-password-length</mat-label>
<input matInput type="number" formControlName="maximumLength" step="1" min="6"/>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.maximumLength').hasError('min')">
{{ 'admin.maximum-password-length-min' | translate }}
</mat-error>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.maximumLength').hasError('lessMin')">
{{ 'admin.maximum-password-length-less-min' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.minimum-uppercase-letters</mat-label>
@ -140,9 +152,14 @@
</mat-error>
</mat-form-field>
</div>
<mat-checkbox formControlName="allowWhitespaces" style="margin-bottom: 16px">
<mat-label translate>admin.allow-whitespace</mat-label>
</mat-checkbox>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-checkbox fxFlex formControlName="allowWhitespaces" style="margin-bottom: 16px">
<mat-label translate>admin.allow-whitespace</mat-label>
</mat-checkbox>
<mat-checkbox fxFlex formControlName="forceUserToResetPasswordIfNotValid" style="margin-bottom: 16px">
<mat-label translate>admin.force-reset-password-if-no-valid</mat-label>
</mat-checkbox>
</div>
</section>
</fieldset>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap" style="margin-top: 16px">

27
ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts

@ -19,7 +19,14 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { Router } from '@angular/router';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import {
AbstractControl,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup, ValidationErrors,
ValidatorFn,
Validators
} from '@angular/forms';
import { JwtSettings, SecuritySettings } from '@shared/models/settings.models';
import { AdminService } from '@core/http/admin.service';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
@ -67,14 +74,16 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi
userLockoutNotificationEmail: ['', []],
passwordPolicy: this.fb.group(
{
minimumLength: [null, [Validators.required, Validators.min(5), Validators.max(50)]],
minimumLength: [null, [Validators.required, Validators.min(6), Validators.max(50)]],
maximumLength: [null, [Validators.min(6), this.maxPasswordValidation()]],
minimumUppercaseLetters: [null, Validators.min(0)],
minimumLowercaseLetters: [null, Validators.min(0)],
minimumDigits: [null, Validators.min(0)],
minimumSpecialCharacters: [null, Validators.min(0)],
passwordExpirationPeriodDays: [null, Validators.min(0)],
passwordReuseFrequencyDays: [null, Validators.min(0)],
allowWhitespaces: [true]
allowWhitespaces: [true],
forceUserToResetPasswordIfNotValid: [false]
}
)
});
@ -113,6 +122,18 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi
})).subscribe(() => {});
}
private maxPasswordValidation(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value: string = control.value;
if (value) {
if (value < this.securitySettingsFormGroup.get('passwordPolicy.minimumLength').value) {
return {lessMin: true};
}
}
return null;
};
}
discardSetting() {
this.securitySettingsFormGroup.reset(this.securitySettings);
}

7
ui-ngx/src/app/modules/home/pages/security/security.component.html

@ -105,6 +105,13 @@
<mat-icon class="tb-mat-20" [svgIcon]="changePassword.get('newPassword').hasError('minLength') ? 'mdi:circle-small' : 'mdi:check'"></mat-icon>
{{ 'security.password-requirement.character' | translate : {count: passwordPolicy.minimumLength} }}
</p>
<div *ngIf="passwordPolicy.maximumLength > 0">
<h4 class="mat-h4" translate>security.password-requirement.at-most</h4>
<p class="mat-body">
<mat-icon class="tb-mat-20" [svgIcon]="changePassword.get('newPassword').hasError('maxLength') ? 'mdi:circle-small' : 'mdi:check'"></mat-icon>
{{ 'security.password-requirement.character' | translate : {count: passwordPolicy.maximumLength} }}
</p>
</div>
</div>
</ng-template>
<div fxLayout="row" fxLayoutGap="8px" style="margin-top: 18px;" [fxShow]="changePassword.dirty || changePassword.touched">

5
ui-ngx/src/app/modules/home/pages/security/security.component.ts

@ -213,6 +213,11 @@ export class SecurityComponent extends PageComponent implements OnInit, OnDestro
errors.minLength = true;
}
if (this.passwordPolicy?.maximumLength > 0 &&
(value.length > this.passwordPolicy?.maximumLength || value.length < this.passwordPolicy.minimumLength)) {
errors.maxLength = true;
}
return isEqual(errors, {}) ? null : errors;
};
}

2
ui-ngx/src/app/shared/models/settings.models.ts

@ -99,12 +99,14 @@ export type DeviceConnectivitySettings = Record<DeviceConnectivityProtocol, Devi
export interface UserPasswordPolicy {
minimumLength: number;
maximumLength: number;
minimumUppercaseLetters: number;
minimumLowercaseLetters: number;
minimumDigits: number;
minimumSpecialCharacters: number;
passwordExpirationPeriodDays: number;
allowWhitespaces: boolean;
forceUserToResetPasswordIfNotValid: boolean;
}
export interface SecuritySettings {

9
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -182,7 +182,10 @@
"password-policy": "Password policy",
"minimum-password-length": "Minimum password length",
"minimum-password-length-required": "Minimum password length is required",
"minimum-password-length-range": "Minimum password length should be in a range from 5 to 50",
"minimum-password-length-range": "Minimum password length should be in a range from 6 to 50",
"maximum-password-length": "Maximum password length",
"maximum-password-length-min": "Maximum password length should be at least 6",
"maximum-password-length-less-min": "Maximum password length should be greater than minimum length",
"minimum-uppercase-letters": "Minimum number of uppercase letters",
"minimum-uppercase-letters-range": "Minimum number of uppercase letters can't be negative",
"minimum-lowercase-letters": "Minimum number of lowercase letters",
@ -196,6 +199,7 @@
"password-reuse-frequency-days": "Password reuse frequency in days",
"password-reuse-frequency-days-range": "Password reuse frequency in days can't be negative",
"allow-whitespace": "Allow whitespace",
"force-reset-password-if-no-valid": "Force to reset password if not valid",
"general-policy": "General policy",
"max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked",
"minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative",
@ -3610,7 +3614,8 @@
"password-requirements": "Password requirements",
"password-should-difference": "New password should be different from current",
"special-character": "{ count, plural, =1 {1 special character} other {# special characters} }",
"uppercase-letter": "{ count, plural, =1 {1 uppercase letter} other {# uppercase letters} }"
"uppercase-letter": "{ count, plural, =1 {1 uppercase letter} other {# uppercase letters} }",
"at-most": "At most:"
}
},
"relation": {

Loading…
Cancel
Save