refactor: put the input password in a separate component
Some checks failed
Build and Deploy Angular App / build (push) Failing after 1m17s
Some checks failed
Build and Deploy Angular App / build (push) Failing after 1m17s
This commit is contained in:
parent
6e914caabc
commit
7830c5f21d
@ -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<PasswordPolicy>(request);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
<div [formGroup]="formGroup">
|
||||
<mat-form-field color="accent">
|
||||
<mat-label>Пароль</mat-label>
|
||||
<input matInput
|
||||
matTooltip="Укажите пароль"
|
||||
formControlName="password"
|
||||
required
|
||||
[type]="hidePass ? 'password' : 'text'"
|
||||
id="passwordNextFocus"
|
||||
focusNext="focusNext">
|
||||
|
||||
<button mat-icon-button matSuffix (click)="togglePassword($event)" [attr.aria-label]="'Hide password'"
|
||||
[attr.aria-pressed]="hidePass">
|
||||
<mat-icon>{{ hidePass ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
|
||||
@if (formGroup.get('password')?.hasError('required')) {
|
||||
<mat-error>
|
||||
Пароль является <i>обязательным</i>
|
||||
</mat-error>
|
||||
}
|
||||
|
||||
@if (formGroup.get('password')?.hasError('minlength')) {
|
||||
<mat-error>
|
||||
Пароль должен быть не менее {{ policy.minimumLength }} символов
|
||||
</mat-error>
|
||||
}
|
||||
|
||||
@if (formGroup.get('password')?.hasError('pattern')) {
|
||||
<mat-error>
|
||||
Пароль должен содержать:
|
||||
@if (policy.requireLettersDifferentCase) {
|
||||
Латинские символы разных регистров
|
||||
} @else if (policy.requireLetter) {
|
||||
Один латинский символ
|
||||
} @else if (policy.requireDigit) {
|
||||
Одну цифру
|
||||
}
|
||||
@if (policy.requireSpecialCharacter) {
|
||||
Специальный символ
|
||||
}
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
@ -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();
|
||||
}
|
||||
}
|
@ -27,35 +27,11 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field color="accent" style="margin-bottom: 20px">
|
||||
<mat-label>Пароль</mat-label>
|
||||
<input matInput
|
||||
matTooltip="Укажите пароль"
|
||||
formControlName="password"
|
||||
required
|
||||
[type]="hidePass ? 'password' : 'text'"
|
||||
id="passwordNextFocus"
|
||||
focusNext="loginNextFocus">
|
||||
|
||||
<button mat-icon-button matSuffix (click)="togglePassword($event)" [attr.aria-label]="'Hide password'"
|
||||
[attr.aria-pressed]="hidePass">
|
||||
<mat-icon>{{ hidePass ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
|
||||
@if (loginForm.get('password')?.hasError('required')) {
|
||||
<mat-error>
|
||||
Пароль является <i>обязательным</i>
|
||||
</mat-error>
|
||||
}
|
||||
|
||||
@if (loginForm.get('password')?.hasError('minlength')) {
|
||||
<mat-error>
|
||||
Пароль должен быть не менее 8 символов
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<password-input [focusNext]="'loginNextFocus'" [formGroup]="loginForm"/>
|
||||
</form>
|
||||
|
||||
<OAuthProviders/>
|
||||
|
||||
<mat-error>
|
||||
{{ errorText }}
|
||||
</mat-error>
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -53,47 +53,7 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field color="accent" style="margin-bottom: 20px">
|
||||
<mat-label>Пароль</mat-label>
|
||||
<input matInput
|
||||
matTooltip="Укажите пароль"
|
||||
formControlName="password"
|
||||
required
|
||||
[type]="hidePass ? 'password' : 'text'">
|
||||
|
||||
<button mat-icon-button matSuffix (click)="togglePassword($event)" [attr.aria-label]="'Hide password'"
|
||||
[attr.aria-pressed]="hidePass">
|
||||
<mat-icon>{{ hidePass ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
|
||||
@if (createAdminForm.get('password')?.hasError('required')) {
|
||||
<mat-error>
|
||||
Пароль является <i>обязательным</i>
|
||||
</mat-error>
|
||||
}
|
||||
|
||||
@if (createAdminForm.get('password')?.hasError('minlength')) {
|
||||
<mat-error>
|
||||
Пароль должен быть не менее {{ policy.minimumLength }} символов
|
||||
</mat-error>
|
||||
}
|
||||
|
||||
@if (createAdminForm.get('password')?.hasError('pattern')) {
|
||||
<mat-error>
|
||||
Пароль должен содержать:
|
||||
@if (policy.requireLettersDifferentCase) {
|
||||
* Латинские символы разных регистров
|
||||
} @else if (policy.requireLetter) {
|
||||
* Один латинский символ
|
||||
} @else if (policy.requireDigit) {
|
||||
* Одну цифру
|
||||
}
|
||||
@if (policy.requireSpecialCharacter) {
|
||||
* специальный символ
|
||||
}
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<password-input [formGroup]="createAdminForm" [isSetupMode]="true"/>
|
||||
|
||||
<mat-form-field color="accent">
|
||||
<mat-label>Повторите пароль</mat-label>
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user