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 {Injectable} from "@angular/core";
|
||||||
import ApiService, {AvailableVersion} from "@api/api.service";
|
import ApiService, {AvailableVersion} from "@api/api.service";
|
||||||
|
import {PasswordPolicy} from "@model/passwordPolicy";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class SecurityService extends ApiService {
|
export default class SecurityService extends ApiService {
|
||||||
@ -14,4 +15,12 @@ export default class SecurityService extends ApiService {
|
|||||||
|
|
||||||
return this.combinedUrl(request);
|
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>
|
||||||
|
|
||||||
<mat-form-field color="accent" style="margin-bottom: 20px">
|
<password-input [focusNext]="'loginNextFocus'" [formGroup]="loginForm"/>
|
||||||
<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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<OAuthProviders/>
|
||||||
|
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ errorText }}
|
{{ errorText }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
|
@ -3,8 +3,7 @@ import {MatSidenavContainer} from "@angular/material/sidenav";
|
|||||||
import {MatFormFieldModule} from "@angular/material/form-field";
|
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||||
import {MatInput} from "@angular/material/input";
|
import {MatInput} from "@angular/material/input";
|
||||||
import {MatTooltip} from "@angular/material/tooltip";
|
import {MatTooltip} from "@angular/material/tooltip";
|
||||||
import {MatIcon} from "@angular/material/icon";
|
import {MatButton} from "@angular/material/button";
|
||||||
import {MatButton, MatIconButton} from "@angular/material/button";
|
|
||||||
import {MatCard} from "@angular/material/card";
|
import {MatCard} from "@angular/material/card";
|
||||||
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||||
import {FocusNextDirective} from "@/directives/focus-next.directive";
|
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 AuthApiService from "@api/v1/authApiService";
|
||||||
import {Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
import {catchError} from "rxjs";
|
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({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@ -21,13 +23,13 @@ import {catchError} from "rxjs";
|
|||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInput,
|
MatInput,
|
||||||
MatTooltip,
|
MatTooltip,
|
||||||
MatIcon,
|
|
||||||
MatIconButton,
|
|
||||||
MatButton,
|
MatButton,
|
||||||
MatCard,
|
MatCard,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FocusNextDirective,
|
FocusNextDirective,
|
||||||
DataSpinnerComponent
|
DataSpinnerComponent,
|
||||||
|
PasswordInputComponent,
|
||||||
|
OAuthProviders
|
||||||
],
|
],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.css',
|
styleUrl: './login.component.css',
|
||||||
@ -35,7 +37,6 @@ import {catchError} from "rxjs";
|
|||||||
})
|
})
|
||||||
export class LoginComponent {
|
export class LoginComponent {
|
||||||
protected loginForm!: FormGroup;
|
protected loginForm!: FormGroup;
|
||||||
protected hidePass: boolean = true;
|
|
||||||
protected loaderActive: boolean = false;
|
protected loaderActive: boolean = false;
|
||||||
protected loginButtonIsDisable: boolean = true;
|
protected loginButtonIsDisable: boolean = true;
|
||||||
protected errorText: string = '';
|
protected errorText: string = '';
|
||||||
@ -68,11 +69,6 @@ export class LoginComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected togglePassword(event: MouseEvent) {
|
|
||||||
this.hidePass = !this.hidePass;
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected login() {
|
protected login() {
|
||||||
this.loaderActive = true;
|
this.loaderActive = true;
|
||||||
|
|
||||||
@ -82,14 +78,18 @@ export class LoginComponent {
|
|||||||
})
|
})
|
||||||
.pipe(catchError(error => {
|
.pipe(catchError(error => {
|
||||||
this.loaderActive = false;
|
this.loaderActive = false;
|
||||||
this.errorText = error.error instanceof String ? error.statusText : error.error;
|
this.errorText = error.error.detail;
|
||||||
this.loginButtonIsDisable = true;
|
this.loginButtonIsDisable = true;
|
||||||
throw error;
|
throw error;
|
||||||
}))
|
}))
|
||||||
.subscribe(_ => {
|
.subscribe(x => {
|
||||||
this.loaderActive = false;
|
this.loaderActive = false;
|
||||||
this.errorText = '';
|
this.errorText = '';
|
||||||
|
|
||||||
|
if (x == TwoFactorAuthentication.None)
|
||||||
this.router.navigate(['admin']).then();
|
this.router.navigate(['admin']).then();
|
||||||
|
else
|
||||||
|
this.router.navigate(['two-factor']).then();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,47 +53,7 @@
|
|||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field color="accent" style="margin-bottom: 20px">
|
<password-input [formGroup]="createAdminForm" [isSetupMode]="true"/>
|
||||||
<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>
|
|
||||||
|
|
||||||
<mat-form-field color="accent">
|
<mat-form-field color="accent">
|
||||||
<mat-label>Повторите пароль</mat-label>
|
<mat-label>Повторите пароль</mat-label>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
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 {NavigationService} from "@service/navigation.service";
|
||||||
import {passwordMatchValidator} from '@service/password-match.validator';
|
import {passwordMatchValidator} from '@service/password-match.validator';
|
||||||
import SetupService from "@api/v1/setup.service";
|
import SetupService from "@api/v1/setup.service";
|
||||||
@ -10,9 +10,9 @@ import {MatTooltip} from "@angular/material/tooltip";
|
|||||||
import {MatIconButton} from "@angular/material/button";
|
import {MatIconButton} from "@angular/material/button";
|
||||||
import {MatIcon} from "@angular/material/icon";
|
import {MatIcon} from "@angular/material/icon";
|
||||||
import AuthApiService from "@api/v1/authApiService";
|
import AuthApiService from "@api/v1/authApiService";
|
||||||
import {PasswordPolicy} from "@model/passwordPolicy";
|
|
||||||
import {OAuthProviders} from "@component/OAuthProviders/OAuthProviders";
|
import {OAuthProviders} from "@component/OAuthProviders/OAuthProviders";
|
||||||
import {OAuthProvider} from "@model/oAuthProvider";
|
import {OAuthProvider} from "@model/oAuthProvider";
|
||||||
|
import {PasswordInputComponent} from "@component/common/password-input/password-input.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create-admin',
|
selector: 'app-create-admin',
|
||||||
@ -25,7 +25,8 @@ import {OAuthProvider} from "@model/oAuthProvider";
|
|||||||
MatTooltip,
|
MatTooltip,
|
||||||
MatIconButton,
|
MatIconButton,
|
||||||
MatIcon,
|
MatIcon,
|
||||||
OAuthProviders
|
OAuthProviders,
|
||||||
|
PasswordInputComponent
|
||||||
],
|
],
|
||||||
templateUrl: './create-admin.component.html',
|
templateUrl: './create-admin.component.html',
|
||||||
providers: [AuthApiService]
|
providers: [AuthApiService]
|
||||||
@ -33,9 +34,7 @@ import {OAuthProvider} from "@model/oAuthProvider";
|
|||||||
|
|
||||||
export class CreateAdminComponent {
|
export class CreateAdminComponent {
|
||||||
protected createAdminForm!: FormGroup;
|
protected createAdminForm!: FormGroup;
|
||||||
protected hidePass = true;
|
|
||||||
protected hideRetypePass = true;
|
protected hideRetypePass = true;
|
||||||
protected policy!: PasswordPolicy;
|
|
||||||
protected activatedProviders: OAuthProvider[] = [];
|
protected activatedProviders: OAuthProvider[] = [];
|
||||||
|
|
||||||
constructor(
|
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();
|
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) {
|
protected toggleRetypePassword(event: MouseEvent) {
|
||||||
this.hideRetypePass = !this.hideRetypePass;
|
this.hideRetypePass = !this.hideRetypePass;
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user