From 7830c5f21dce864cc600d4a8375a4cbc1b67316e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:41:28 +0300 Subject: [PATCH] refactor: put the input password in a separate component --- src/api/v1/securityService.ts | 9 +++ .../password-input.component.html | 45 +++++++++++ .../password-input.component.ts | 77 +++++++++++++++++++ src/pages/login/login.component.html | 30 +------- src/pages/login/login.component.ts | 28 +++---- .../create-admin/create-admin.component.html | 42 +--------- .../create-admin/create-admin.component.ts | 43 +---------- 7 files changed, 153 insertions(+), 121 deletions(-) create mode 100644 src/components/common/password-input/password-input.component.html create mode 100644 src/components/common/password-input/password-input.component.ts diff --git a/src/api/v1/securityService.ts b/src/api/v1/securityService.ts index ef1a2f6..08a309c 100644 --- a/src/api/v1/securityService.ts +++ b/src/api/v1/securityService.ts @@ -1,5 +1,6 @@ import {Injectable} from "@angular/core"; import ApiService, {AvailableVersion} from "@api/api.service"; +import {PasswordPolicy} from "@model/passwordPolicy"; @Injectable() export default class SecurityService extends ApiService { @@ -14,4 +15,12 @@ export default class SecurityService extends ApiService { return this.combinedUrl(request); } + + public passwordPolicy() { + let request = this.createRequestBuilder() + .setEndpoint('PasswordPolicy') + .build; + + return this.get(request); + } } diff --git a/src/components/common/password-input/password-input.component.html b/src/components/common/password-input/password-input.component.html new file mode 100644 index 0000000..46a0c5f --- /dev/null +++ b/src/components/common/password-input/password-input.component.html @@ -0,0 +1,45 @@ +
+ + Пароль + + + + + @if (formGroup.get('password')?.hasError('required')) { + + Пароль является обязательным + + } + + @if (formGroup.get('password')?.hasError('minlength')) { + + Пароль должен быть не менее {{ policy.minimumLength }} символов + + } + + @if (formGroup.get('password')?.hasError('pattern')) { + + Пароль должен содержать: + @if (policy.requireLettersDifferentCase) { + Латинские символы разных регистров + } @else if (policy.requireLetter) { + Один латинский символ + } @else if (policy.requireDigit) { + Одну цифру + } + @if (policy.requireSpecialCharacter) { + Специальный символ + } + + } + +
diff --git a/src/components/common/password-input/password-input.component.ts b/src/components/common/password-input/password-input.component.ts new file mode 100644 index 0000000..d5735fc --- /dev/null +++ b/src/components/common/password-input/password-input.component.ts @@ -0,0 +1,77 @@ +import {Component, Input} from '@angular/core'; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInput} from "@angular/material/input"; +import {MatIconButton} from "@angular/material/button"; +import {FormGroup, ReactiveFormsModule, ValidatorFn, Validators} from "@angular/forms"; +import {MatSelectModule} from "@angular/material/select"; +import {MatTooltip} from "@angular/material/tooltip"; +import {MatIcon} from "@angular/material/icon"; +import {PasswordPolicy} from "@model/passwordPolicy"; +import SecurityService from "@api/v1/securityService"; +import {FocusNextDirective} from "@/directives/focus-next.directive"; +import SetupService from "@api/v1/setup.service"; + +@Component({ + selector: 'password-input', + imports: [ + ReactiveFormsModule, + MatFormFieldModule, + MatSelectModule, + MatInput, + MatTooltip, + MatIconButton, + MatIcon, + FocusNextDirective + ], + templateUrl: './password-input.component.html', + styleUrl: './password-input.component.css', + providers: [SecurityService, SetupService] +}) +export class PasswordInputComponent { + protected hidePass = true; + protected policy!: PasswordPolicy; + @Input() formGroup!: FormGroup; + @Input() focusNext: string | undefined; + @Input() isSetupMode: boolean = false; + + constructor(securityApi: SecurityService, setupApi: SetupService) { + if (this.isSetupMode) + setupApi.passwordPolicyConfiguration().subscribe(policy => { + this.policy = policy; + this.updateValueAndValidity(policy); + }); + else + securityApi.passwordPolicy().subscribe(policy => { + this.policy = policy; + this.updateValueAndValidity(policy); + }); + } + + private updateValueAndValidity(policy: PasswordPolicy): void { + const validators: ValidatorFn[] = [Validators.required]; + + if (policy.minimumLength) { + validators.push(Validators.minLength(policy.minimumLength)); + } + + if (policy.requireLettersDifferentCase) { + validators.push(Validators.pattern(/(?=.*[a-z])(?=.*[A-Z])/)); + } else if (policy.requireLetter) { + validators.push(Validators.pattern(/[A-Za-z]/)); + } else if (policy.requireDigit) { + validators.push(Validators.pattern(/\d/)); + } + + if (policy.requireSpecialCharacter) { + validators.push(Validators.pattern(/[!@#$%^&*(),.?":{}|<>]/)); + } + + this.formGroup.get('password')?.setValidators(validators); + this.formGroup.get('password')?.updateValueAndValidity(); + } + + protected togglePassword(event: MouseEvent) { + this.hidePass = !this.hidePass; + event.stopPropagation(); + } +} diff --git a/src/pages/login/login.component.html b/src/pages/login/login.component.html index 3b2bb16..51f45ab 100644 --- a/src/pages/login/login.component.html +++ b/src/pages/login/login.component.html @@ -27,35 +27,11 @@ } - - Пароль - - - - - @if (loginForm.get('password')?.hasError('required')) { - - Пароль является обязательным - - } - - @if (loginForm.get('password')?.hasError('minlength')) { - - Пароль должен быть не менее 8 символов - - } - + + + {{ errorText }} diff --git a/src/pages/login/login.component.ts b/src/pages/login/login.component.ts index fb22b30..209b064 100644 --- a/src/pages/login/login.component.ts +++ b/src/pages/login/login.component.ts @@ -3,8 +3,7 @@ import {MatSidenavContainer} from "@angular/material/sidenav"; import {MatFormFieldModule} from "@angular/material/form-field"; import {MatInput} from "@angular/material/input"; import {MatTooltip} from "@angular/material/tooltip"; -import {MatIcon} from "@angular/material/icon"; -import {MatButton, MatIconButton} from "@angular/material/button"; +import {MatButton} from "@angular/material/button"; import {MatCard} from "@angular/material/card"; import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {FocusNextDirective} from "@/directives/focus-next.directive"; @@ -12,6 +11,9 @@ import {DataSpinnerComponent} from "@component/common/data-spinner/data-spinner. import AuthApiService from "@api/v1/authApiService"; import {Router} from "@angular/router"; import {catchError} from "rxjs"; +import {TwoFactorAuthentication} from "@model/twoFactorAuthentication"; +import {PasswordInputComponent} from "@component/common/password-input/password-input.component"; +import {OAuthProviders} from "@component/OAuthProviders/OAuthProviders"; @Component({ selector: 'app-login', @@ -21,13 +23,13 @@ import {catchError} from "rxjs"; MatFormFieldModule, MatInput, MatTooltip, - MatIcon, - MatIconButton, MatButton, MatCard, ReactiveFormsModule, FocusNextDirective, - DataSpinnerComponent + DataSpinnerComponent, + PasswordInputComponent, + OAuthProviders ], templateUrl: './login.component.html', styleUrl: './login.component.css', @@ -35,7 +37,6 @@ import {catchError} from "rxjs"; }) export class LoginComponent { protected loginForm!: FormGroup; - protected hidePass: boolean = true; protected loaderActive: boolean = false; protected loginButtonIsDisable: boolean = true; protected errorText: string = ''; @@ -68,11 +69,6 @@ export class LoginComponent { }); } - protected togglePassword(event: MouseEvent) { - this.hidePass = !this.hidePass; - event.stopPropagation(); - } - protected login() { this.loaderActive = true; @@ -82,14 +78,18 @@ export class LoginComponent { }) .pipe(catchError(error => { this.loaderActive = false; - this.errorText = error.error instanceof String ? error.statusText : error.error; + this.errorText = error.error.detail; this.loginButtonIsDisable = true; throw error; })) - .subscribe(_ => { + .subscribe(x => { this.loaderActive = false; this.errorText = ''; - this.router.navigate(['admin']).then(); + + if (x == TwoFactorAuthentication.None) + this.router.navigate(['admin']).then(); + else + this.router.navigate(['two-factor']).then(); }); } } diff --git a/src/pages/setup/create-admin/create-admin.component.html b/src/pages/setup/create-admin/create-admin.component.html index e650d88..ca5c71a 100644 --- a/src/pages/setup/create-admin/create-admin.component.html +++ b/src/pages/setup/create-admin/create-admin.component.html @@ -53,47 +53,7 @@ } - - Пароль - - - - - @if (createAdminForm.get('password')?.hasError('required')) { - - Пароль является обязательным - - } - - @if (createAdminForm.get('password')?.hasError('minlength')) { - - Пароль должен быть не менее {{ policy.minimumLength }} символов - - } - - @if (createAdminForm.get('password')?.hasError('pattern')) { - - Пароль должен содержать: - @if (policy.requireLettersDifferentCase) { - * Латинские символы разных регистров - } @else if (policy.requireLetter) { - * Один латинский символ - } @else if (policy.requireDigit) { - * Одну цифру - } - @if (policy.requireSpecialCharacter) { - * специальный символ - } - - } - + Повторите пароль diff --git a/src/pages/setup/create-admin/create-admin.component.ts b/src/pages/setup/create-admin/create-admin.component.ts index 1582cba..9df434e 100644 --- a/src/pages/setup/create-admin/create-admin.component.ts +++ b/src/pages/setup/create-admin/create-admin.component.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule, ValidatorFn, Validators} from "@angular/forms"; +import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {NavigationService} from "@service/navigation.service"; import {passwordMatchValidator} from '@service/password-match.validator'; import SetupService from "@api/v1/setup.service"; @@ -10,9 +10,9 @@ import {MatTooltip} from "@angular/material/tooltip"; import {MatIconButton} from "@angular/material/button"; import {MatIcon} from "@angular/material/icon"; import AuthApiService from "@api/v1/authApiService"; -import {PasswordPolicy} from "@model/passwordPolicy"; import {OAuthProviders} from "@component/OAuthProviders/OAuthProviders"; import {OAuthProvider} from "@model/oAuthProvider"; +import {PasswordInputComponent} from "@component/common/password-input/password-input.component"; @Component({ selector: 'app-create-admin', @@ -25,7 +25,8 @@ import {OAuthProvider} from "@model/oAuthProvider"; MatTooltip, MatIconButton, MatIcon, - OAuthProviders + OAuthProviders, + PasswordInputComponent ], templateUrl: './create-admin.component.html', providers: [AuthApiService] @@ -33,9 +34,7 @@ import {OAuthProvider} from "@model/oAuthProvider"; export class CreateAdminComponent { protected createAdminForm!: FormGroup; - protected hidePass = true; protected hideRetypePass = true; - protected policy!: PasswordPolicy; protected activatedProviders: OAuthProvider[] = []; constructor( @@ -63,13 +62,6 @@ export class CreateAdminComponent { ); }; - this.api.passwordPolicyConfiguration().subscribe(policy => { - this.policy = policy; - const passwordValidators = this.createPasswordValidators(policy); - this.createAdminForm.get('password')?.setValidators(passwordValidators); - this.createAdminForm.get('password')?.updateValueAndValidity(); - }); - this.updateAdminData(); } @@ -87,33 +79,6 @@ export class CreateAdminComponent { }); } - private createPasswordValidators(policy: PasswordPolicy): ValidatorFn[] { - const validators: ValidatorFn[] = [Validators.required]; - - if (policy.minimumLength) { - validators.push(Validators.minLength(policy.minimumLength)); - } - - if (policy.requireLettersDifferentCase) { - validators.push(Validators.pattern(/(?=.*[a-z])(?=.*[A-Z])/)); - } else if (policy.requireLetter) { - validators.push(Validators.pattern(/[A-Za-z]/)); - } else if (policy.requireDigit) { - validators.push(Validators.pattern(/\d/)); - } - - if (policy.requireSpecialCharacter) { - validators.push(Validators.pattern(/[!@#$%^&*(),.?":{}|<>]/)); - } - - return validators; - } - - protected togglePassword(event: MouseEvent) { - this.hidePass = !this.hidePass; - event.stopPropagation(); - } - protected toggleRetypePassword(event: MouseEvent) { this.hideRetypePass = !this.hideRetypePass; event.stopPropagation();