diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts index 463a7b01dc..65b759a21e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts @@ -13,4 +13,8 @@ export const DEFAULT_VALIDATION_BLUEPRINTS = { range: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', required: 'AbpValidation::ThisFieldIsRequired.', url: 'AbpValidation::ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl', + passwordRequiresLower: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresLower', + passwordRequiresUpper: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresUpper', + passwordRequiresDigit: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresDigit', + passwordRequiresNonAlphanumeric: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresNonAlphanumeric', }; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts index ca6aa74124..4cb240e9ab 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/index.ts @@ -4,3 +4,4 @@ export * from './nav-item'; export * from './statistics'; export * from './toaster'; export * from './user-menu'; +export * from './validation'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/validation.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/validation.ts new file mode 100644 index 0000000000..bcc53a34e7 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/validation.ts @@ -0,0 +1 @@ +export type PasswordRule = 'small' | 'capital' | 'number' | 'special'; 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 index fe7577477c..435afb76e6 100644 --- 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 @@ -4,10 +4,11 @@ import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; import { Validators } from '@angular/forms'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { validatePassword } from '@ngx-validate/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { of } from 'rxjs'; -import { getPasswordValidators } from '../utils'; +import { getPasswordValidators, validatePassword } from '../utils'; +import { PasswordRule } from '../models/validation'; + @Component({ template: '', selector: 'abp-dummy' }) class DummyComponent {} @@ -47,8 +48,11 @@ describe('ValidationUtils', () => { configState.refreshAppState(); const validators = getPasswordValidators(spectator.inject(Injector)); + const passwordValidators = ['number', 'small', 'capital', 'special'].map( + (rule: PasswordRule) => validatePassword(rule), + ); const expectedValidators = [ - validatePassword(['number', 'small', 'capital', 'special']), + ...passwordValidators, Validators.minLength(6), Validators.maxLength(128), ]; 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 a9ec1d4270..cb201d224f 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,7 +1,8 @@ import { ABP, ConfigStateService } from '@abp/ng.core'; import { Injector } from '@angular/core'; -import { ValidatorFn, Validators } from '@angular/forms'; -import { PasswordRules, validatePassword } from '@ngx-validate/core'; +import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'; +import { normalizeDiacritics, PasswordRules } from '@ngx-validate/core'; +import { PasswordRule } from "../models/validation"; const { minLength, maxLength } = Validators; @@ -31,7 +32,8 @@ export function getPasswordValidators(injector: Injector): ValidatorFn[] { requiredLength = +getRule('RequiredLength'); } - return [validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)]; + const passwordValidators = passwordRulesArr.map(rule => validatePassword(rule)); + return [...passwordValidators, minLength(requiredLength), maxLength(128)]; } function getRuleFn(injector: Injector) { @@ -43,3 +45,39 @@ function getRuleFn(injector: Injector) { return (passwordRules[`Abp.Identity.Password.${key}`] || '').toLowerCase(); }; } +const errorMessageMap = { + small: 'passwordRequiresLower', + capital: 'passwordRequiresUpper', + number: 'passwordRequiresDigit', + special: 'passwordRequiresNonAlphanumeric', +}; + +export function validatePassword( + shouldContain: PasswordRule +): ValidatorFn { + return (control: AbstractControl) => { + if (!control.value) return null; + + const value = normalizeDiacritics(control.value); + + const regexMap = { + small: /.*[a-z].*/, + capital: /.*[A-Z].*/, + number: /.*[0-9].*/, + special: /.*[^0-9a-zA-Z].*/, + }; + const regex = regexMap[shouldContain]; + + const isValid = regex.test(value); + + if (isValid) { + return null; + } + + const error = errorMessageMap[shouldContain]; + + return { + [error]: true, + }; + }; +}