From cabb20b5f1a2645547429685fe20d1035a9bf635 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 8 Apr 2020 15:39:58 +0300 Subject: [PATCH 1/3] feat(theme-shared): create getValidators utility function #3514 --- .../src/lib/tests/validation-utils.spec.ts | 52 +++++++++++++++++++ .../theme-shared/src/lib/utils/index.ts | 1 + .../src/lib/utils/validation-utils.ts | 45 ++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts new file mode 100644 index 0000000000..3cda3b5403 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts @@ -0,0 +1,52 @@ +import { ConfigState } from '@abp/ng.core'; +import { Component } from '@angular/core'; +import { createComponentFactory, Spectator } from '@ngneat/spectator'; +import { NgxValidateCoreModule, validatePassword } from '@ngx-validate/core'; +import { NgxsModule, Store } from '@ngxs/store'; +import { HttpClient } from '@angular/common/http'; +import { getPasswordValidators } from '../utils'; +import { Validators } from '@angular/forms'; + +@Component({ template: '', selector: 'abp-dummy' }) +class DummyComponent {} + +describe('ValidationUtils', () => { + let spectator: Spectator; + const createComponent = createComponentFactory({ + component: DummyComponent, + imports: [NgxsModule.forRoot([ConfigState]), NgxValidateCoreModule.forRoot()], + mocks: [HttpClient], + }); + + beforeEach(() => (spectator = createComponent())); + + describe('#getPasswordValidators', () => { + it('should return password valdiators', () => { + const store = spectator.get(Store); + store.reset({ + ConfigState: { + setting: { + values: { + 'Abp.Identity.Password.RequiredLength': '6', + 'Abp.Identity.Password.RequiredUniqueChars': '1', + 'Abp.Identity.Password.RequireNonAlphanumeric': 'True', + 'Abp.Identity.Password.RequireLowercase': 'True', + 'Abp.Identity.Password.RequireUppercase': 'True', + 'Abp.Identity.Password.RequireDigit': 'True', + }, + }, + }, + }); + const validators = getPasswordValidators(store); + const expectedValidators = [ + validatePassword(['number', 'small', 'capital', 'special']), + Validators.minLength(6), + Validators.maxLength(128), + ]; + + validators.forEach((validator, index) => { + expect(validator.toString()).toBe(expectedValidators[index].toString()); + }); + }); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts index 3742880cb7..f50cf2ffe0 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts @@ -1,2 +1,3 @@ export * from './widget-utils'; export * from './date-parser-formatter'; +export * from './validation-utils'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts new file mode 100644 index 0000000000..c1067a0042 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts @@ -0,0 +1,45 @@ +import { Store } from '@ngxs/store'; +import { ABP, ConfigState } from '@abp/ng.core'; +import { PasswordRules, validatePassword } from '@ngx-validate/core'; +import { Validators } from '@angular/forms'; + +const { minLength, maxLength } = Validators; + +export function getPasswordValidators(store: Store) { + const getRule = getRuleFn(store); + + const passwordRulesArr = [] as PasswordRules; + let requiredLength = 1; + + if (getRule('RequireDigit') === 'true') { + passwordRulesArr.push('number'); + } + + if (getRule('RequireLowercase') === 'true') { + passwordRulesArr.push('small'); + } + + if (getRule('RequireUppercase') === 'true') { + passwordRulesArr.push('capital'); + } + + if (getRule('RequireNonAlphanumeric') === 'true') { + passwordRulesArr.push('special'); + } + + if (Number.isInteger(+getRule('RequiredLength'))) { + requiredLength = +getRule('RequiredLength'); + } + + return [validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)]; +} + +function getRuleFn(store: Store) { + return (key: string) => { + const passwordRules: ABP.Dictionary = store.selectSnapshot( + ConfigState.getSettings('Identity.Password'), + ); + + return (passwordRules[`Abp.Identity.Password.${key}`] || '').toLowerCase(); + }; +} From 31f13a1b27b2de15bbad7f181e444be3e09cdac8 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 8 Apr 2020 15:40:51 +0300 Subject: [PATCH 2/3] refactor: get password validators from getPasswordValidators fn #3514 --- .../change-password.component.ts | 52 +++---------------- .../components/register/register.component.ts | 46 +++------------- .../lib/components/users/users.component.ts | 43 ++------------- 3 files changed, 18 insertions(+), 123 deletions(-) diff --git a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts index 14ed582d95..c2b02c197b 100644 --- a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts @@ -1,14 +1,14 @@ -import { ChangePassword, ConfigState, ABP } from '@abp/ng.core'; -import { ToasterService } from '@abp/ng.theme.shared'; +import { ChangePassword } from '@abp/ng.core'; +import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { comparePasswords, Validation, PasswordRules, validatePassword } from '@ngx-validate/core'; +import { comparePasswords, Validation } from '@ngx-validate/core'; import { Store } from '@ngxs/store'; -import snq from 'snq'; import { finalize } from 'rxjs/operators'; +import snq from 'snq'; import { Account } from '../../models/account'; -const { minLength, required, maxLength } = Validators; +const { required } = Validators; const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword']; @@ -36,33 +36,7 @@ export class ChangePasswordComponent ) {} ngOnInit(): void { - const passwordRules: ABP.Dictionary = this.store.selectSnapshot( - ConfigState.getSettings('Identity.Password'), - ); - const passwordRulesArr = [] as PasswordRules; - let requiredLength = 1; - - if ((passwordRules['Abp.Identity.Password.RequireDigit'] || '').toLowerCase() === 'true') { - passwordRulesArr.push('number'); - } - - if ((passwordRules['Abp.Identity.Password.RequireLowercase'] || '').toLowerCase() === 'true') { - passwordRulesArr.push('small'); - } - - if ((passwordRules['Abp.Identity.Password.RequireUppercase'] || '').toLowerCase() === 'true') { - passwordRulesArr.push('capital'); - } - - if ( - (passwordRules['Abp.Identity.Password.RequireNonAlphanumeric'] || '').toLowerCase() === 'true' - ) { - passwordRulesArr.push('special'); - } - - if (Number.isInteger(+passwordRules['Abp.Identity.Password.RequiredLength'])) { - requiredLength = +passwordRules['Abp.Identity.Password.RequiredLength']; - } + const passwordValidations = getPasswordValidators(this.store); this.form = this.fb.group( { @@ -70,23 +44,13 @@ export class ChangePasswordComponent newPassword: [ '', { - validators: [ - required, - validatePassword(passwordRulesArr), - minLength(requiredLength), - maxLength(128), - ], + validators: [required, ...passwordValidations], }, ], repeatNewPassword: [ '', { - validators: [ - required, - validatePassword(passwordRulesArr), - minLength(requiredLength), - maxLength(128), - ], + validators: [required, ...passwordValidations], }, ], }, diff --git a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts index 6785fc67fd..c9ff280792 100644 --- a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts @@ -1,18 +1,15 @@ -import { ConfigState, GetAppConfiguration, ABP, SessionState, AuthService } from '@abp/ng.core'; -import { ToasterService } from '@abp/ng.theme.shared'; +import { AuthService, ConfigState } from '@abp/ng.core'; +import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Navigate } from '@ngxs/router-plugin'; import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; -import { from, throwError } from 'rxjs'; -import { catchError, finalize, switchMap, take, tap } from 'rxjs/operators'; +import { throwError } from 'rxjs'; +import { catchError, finalize, switchMap } from 'rxjs/operators'; import snq from 'snq'; import { RegisterRequest } from '../../models'; import { AccountService } from '../../services/account.service'; -import { PasswordRules, validatePassword } from '@ngx-validate/core'; -import { HttpHeaders } from '@angular/common/http'; -const { maxLength, minLength, required, email } = Validators; +const { maxLength, required, email } = Validators; @Component({ selector: 'abp-register', @@ -53,40 +50,9 @@ export class RegisterComponent implements OnInit { return; } - const passwordRules: ABP.Dictionary = this.store.selectSnapshot( - ConfigState.getSettings('Identity.Password'), - ); - const passwordRulesArr = [] as PasswordRules; - let requiredLength = 1; - - if ((passwordRules['Abp.Identity.Password.RequireDigit'] || '').toLowerCase() === 'true') { - passwordRulesArr.push('number'); - } - - if ((passwordRules['Abp.Identity.Password.RequireLowercase'] || '').toLowerCase() === 'true') { - passwordRulesArr.push('small'); - } - - if ((passwordRules['Abp.Identity.Password.RequireUppercase'] || '').toLowerCase() === 'true') { - passwordRulesArr.push('capital'); - } - - if ( - (passwordRules['Abp.Identity.Password.RequireNonAlphanumeric'] || '').toLowerCase() === 'true' - ) { - passwordRulesArr.push('special'); - } - - if (Number.isInteger(+passwordRules['Abp.Identity.Password.RequiredLength'])) { - requiredLength = +passwordRules['Abp.Identity.Password.RequiredLength']; - } - this.form = this.fb.group({ username: ['', [required, maxLength(255)]], - password: [ - '', - [required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)], - ], + password: ['', [required, ...getPasswordValidators(this.store)]], email: ['', [required, email]], }); } diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 0be2c3d66d..1e943a6be6 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -1,5 +1,5 @@ -import { ABP, ConfigState } from '@abp/ng.core'; -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; +import { ABP } from '@abp/ng.core'; +import { Confirmation, ConfirmationService, getPasswordValidators } from '@abp/ng.theme.shared'; import { Component, OnInit, TemplateRef, TrackByFunction, ViewChild } from '@angular/core'; import { AbstractControl, @@ -9,7 +9,6 @@ import { FormGroup, Validators, } from '@angular/forms'; -import { PasswordRules, validatePassword } from '@ngx-validate/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize, pluck, switchMap, take } from 'rxjs/operators'; @@ -23,8 +22,8 @@ import { UpdateUser, } from '../../actions/identity.actions'; import { Identity } from '../../models/identity'; -import { IdentityState } from '../../states/identity.state'; import { IdentityService } from '../../services/identity.service'; +import { IdentityState } from '../../states/identity.state'; @Component({ selector: 'abp-users', templateUrl: './users.component.html', @@ -63,10 +62,6 @@ export class UsersComponent implements OnInit { sortKey = ''; - passwordRulesArr = [] as PasswordRules; - - requiredPasswordLength = 1; - trackByFn: TrackByFunction = (index, item) => Object.keys(item)[0] || index; onVisiblePermissionChange = event => { @@ -86,32 +81,6 @@ export class UsersComponent implements OnInit { ngOnInit() { this.get(); - - const passwordRules: ABP.Dictionary = this.store.selectSnapshot( - ConfigState.getSettings('Identity.Password'), - ); - - if ((passwordRules['Abp.Identity.Password.RequireDigit'] || '').toLowerCase() === 'true') { - this.passwordRulesArr.push('number'); - } - - if ((passwordRules['Abp.Identity.Password.RequireLowercase'] || '').toLowerCase() === 'true') { - this.passwordRulesArr.push('small'); - } - - if ((passwordRules['Abp.Identity.Password.RequireUppercase'] || '').toLowerCase() === 'true') { - this.passwordRulesArr.push('capital'); - } - - if ( - (passwordRules['Abp.Identity.Password.RequireNonAlphanumeric'] || '').toLowerCase() === 'true' - ) { - this.passwordRulesArr.push('special'); - } - - if (Number.isInteger(+passwordRules['Abp.Identity.Password.RequiredLength'])) { - this.requiredPasswordLength = +passwordRules['Abp.Identity.Password.RequiredLength']; - } } onSearch(value: string) { @@ -146,11 +115,7 @@ export class UsersComponent implements OnInit { ), }); - const passwordValidators = [ - validatePassword(this.passwordRulesArr), - Validators.minLength(this.requiredPasswordLength), - Validators.maxLength(128), - ]; + const passwordValidators = getPasswordValidators(this.store); this.form.addControl('password', new FormControl('', [...passwordValidators])); From 300d366bc62d3d1d757d71fbbc27fc674bbd3eb2 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 8 Apr 2020 15:57:14 +0300 Subject: [PATCH 3/3] feat(tenant-management): add dynamic password validators to admin password field resolves #3513 --- .../src/lib/components/tenants/tenants.component.ts | 4 ++-- .../packages/theme-shared/src/lib/utils/validation-utils.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts index de7a6ad644..4aaac15f96 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts +++ b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts @@ -1,5 +1,5 @@ import { ABP } from '@abp/ng.core'; -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; +import { ConfirmationService, Confirmation, getPasswordValidators } from '@abp/ng.theme.shared'; import { Component, OnInit, TemplateRef, ViewChild, ChangeDetectorRef } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Select, Store } from '@ngxs/store'; @@ -122,7 +122,7 @@ export class TenantsComponent implements OnInit { const tenantForm = this.fb.group({ name: [this.selected.name || '', [Validators.required, Validators.maxLength(256)]], adminEmailAddress: [null, [Validators.required, Validators.maxLength(256), Validators.email]], - adminPassword: [null, [Validators.required]], + adminPassword: [null, [Validators.required, ...getPasswordValidators(this.store)]], }); if (this.hasSelectedTenant) { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts index c1067a0042..966135fad7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts @@ -1,11 +1,11 @@ import { Store } from '@ngxs/store'; import { ABP, ConfigState } from '@abp/ng.core'; import { PasswordRules, validatePassword } from '@ngx-validate/core'; -import { Validators } from '@angular/forms'; +import { Validators, ValidatorFn } from '@angular/forms'; const { minLength, maxLength } = Validators; -export function getPasswordValidators(store: Store) { +export function getPasswordValidators(store: Store): ValidatorFn[] { const getRule = getRuleFn(store); const passwordRulesArr = [] as PasswordRules;