UI: Add JWT security settings form and restyle security page
This commit is contained in:
parent
b776cf13b6
commit
fc1da12999
@ -20,16 +20,18 @@ import { Observable } from 'rxjs';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {
|
import {
|
||||||
AdminSettings,
|
AdminSettings,
|
||||||
RepositorySettings,
|
AutoCommitSettings,
|
||||||
|
JwtSettings,
|
||||||
MailServerSettings,
|
MailServerSettings,
|
||||||
|
RepositorySettings,
|
||||||
|
RepositorySettingsInfo,
|
||||||
SecuritySettings,
|
SecuritySettings,
|
||||||
TestSmsRequest,
|
TestSmsRequest,
|
||||||
UpdateMessage,
|
UpdateMessage
|
||||||
AutoCommitSettings,
|
|
||||||
RepositorySettingsInfo
|
|
||||||
} from '@shared/models/settings.models';
|
} from '@shared/models/settings.models';
|
||||||
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
|
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { LoginResponse } from '@shared/models/login.models';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -70,6 +72,14 @@ export class AdminService {
|
|||||||
defaultHttpOptionsFromConfig(config));
|
defaultHttpOptionsFromConfig(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getJwtSettings(config?: RequestConfig): Observable<JwtSettings> {
|
||||||
|
return this.http.get<JwtSettings>(`/api/admin/jwtSettings`, defaultHttpOptionsFromConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveJwtSettings(jwtSettings: JwtSettings, config?: RequestConfig): Observable<LoginResponse> {
|
||||||
|
return this.http.post<LoginResponse>('/api/admin/jwtSettings', jwtSettings, defaultHttpOptionsFromConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
public getRepositorySettings(config?: RequestConfig): Observable<RepositorySettings> {
|
public getRepositorySettings(config?: RequestConfig): Observable<RepositorySettings> {
|
||||||
return this.http.get<RepositorySettings>(`/api/admin/repositorySettings`, defaultHttpOptionsFromConfig(config));
|
return this.http.get<RepositorySettings>(`/api/admin/repositorySettings`, defaultHttpOptionsFromConfig(config));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,8 +15,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<div>
|
<mat-card class="settings-card">
|
||||||
<mat-card class="settings-card">
|
|
||||||
<mat-card-title>
|
<mat-card-title>
|
||||||
<div fxLayout="row">
|
<div fxLayout="row">
|
||||||
<span class="mat-headline" translate>admin.security-settings</span>
|
<span class="mat-headline" translate>admin.security-settings</span>
|
||||||
@ -30,14 +29,8 @@
|
|||||||
<mat-card-content style="padding-top: 16px;">
|
<mat-card-content style="padding-top: 16px;">
|
||||||
<form [formGroup]="securitySettingsFormGroup" (ngSubmit)="save()" autocomplete="off">
|
<form [formGroup]="securitySettingsFormGroup" (ngSubmit)="save()" autocomplete="off">
|
||||||
<fieldset [disabled]="isLoading$ | async">
|
<fieldset [disabled]="isLoading$ | async">
|
||||||
<div class="mat-accordion-container">
|
<fieldset class="fields-group">
|
||||||
<mat-accordion multi="true">
|
<legend class="group-title" translate>admin.general-policy</legend>
|
||||||
<mat-expansion-panel [expanded]="true">
|
|
||||||
<mat-expansion-panel-header>
|
|
||||||
<mat-panel-title>
|
|
||||||
<div class="tb-panel-title" translate>admin.general-policy</div>
|
|
||||||
</mat-panel-title>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<mat-form-field class="mat-block">
|
<mat-form-field class="mat-block">
|
||||||
<mat-label translate>admin.max-failed-login-attempts</mat-label>
|
<mat-label translate>admin.max-failed-login-attempts</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
@ -53,13 +46,10 @@
|
|||||||
<input matInput type="email"
|
<input matInput type="email"
|
||||||
formControlName="userLockoutNotificationEmail"/>
|
formControlName="userLockoutNotificationEmail"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</mat-expansion-panel>
|
</fieldset>
|
||||||
<mat-expansion-panel [expanded]="true">
|
|
||||||
<mat-expansion-panel-header>
|
<fieldset class="fields-group">
|
||||||
<mat-panel-title>
|
<legend class="group-title" translate>admin.password-policy</legend>
|
||||||
<div class="tb-panel-title" translate>admin.password-policy</div>
|
|
||||||
</mat-panel-title>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<section formGroupName="passwordPolicy">
|
<section formGroupName="passwordPolicy">
|
||||||
<mat-form-field class="mat-block">
|
<mat-form-field class="mat-block">
|
||||||
<mat-label translate>admin.minimum-password-length</mat-label>
|
<mat-label translate>admin.minimum-password-length</mat-label>
|
||||||
@ -79,27 +69,32 @@
|
|||||||
{{ 'admin.minimum-password-length-range' | translate }}
|
{{ 'admin.minimum-password-length-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="mat-block">
|
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>admin.minimum-uppercase-letters</mat-label>
|
<mat-label translate>admin.minimum-uppercase-letters</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
formControlName="minimumUppercaseLetters"
|
formControlName="minimumUppercaseLetters"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"/>
|
min="0"/>
|
||||||
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumUppercaseLetters').hasError('min')">
|
<mat-error
|
||||||
|
*ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumUppercaseLetters').hasError('min')">
|
||||||
{{ 'admin.minimum-uppercase-letters-range' | translate }}
|
{{ 'admin.minimum-uppercase-letters-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="mat-block">
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>admin.minimum-lowercase-letters</mat-label>
|
<mat-label translate>admin.minimum-lowercase-letters</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
formControlName="minimumLowercaseLetters"
|
formControlName="minimumLowercaseLetters"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"/>
|
min="0"/>
|
||||||
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLowercaseLetters').hasError('min')">
|
<mat-error
|
||||||
|
*ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumLowercaseLetters').hasError('min')">
|
||||||
{{ 'admin.minimum-lowercase-letters-range' | translate }}
|
{{ 'admin.minimum-lowercase-letters-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="mat-block">
|
</div>
|
||||||
|
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>admin.minimum-digits</mat-label>
|
<mat-label translate>admin.minimum-digits</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
formControlName="minimumDigits"
|
formControlName="minimumDigits"
|
||||||
@ -109,44 +104,53 @@
|
|||||||
{{ 'admin.minimum-digits-range' | translate }}
|
{{ 'admin.minimum-digits-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="mat-block">
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>admin.minimum-special-characters</mat-label>
|
<mat-label translate>admin.minimum-special-characters</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
formControlName="minimumSpecialCharacters"
|
formControlName="minimumSpecialCharacters"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"/>
|
min="0"/>
|
||||||
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumSpecialCharacters').hasError('min')">
|
<mat-error
|
||||||
|
*ngIf="securitySettingsFormGroup.get('passwordPolicy.minimumSpecialCharacters').hasError('min')">
|
||||||
{{ 'admin.minimum-special-characters-range' | translate }}
|
{{ 'admin.minimum-special-characters-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="mat-block">
|
</div>
|
||||||
|
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>admin.password-expiration-period-days</mat-label>
|
<mat-label translate>admin.password-expiration-period-days</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
formControlName="passwordExpirationPeriodDays"
|
formControlName="passwordExpirationPeriodDays"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"/>
|
min="0"/>
|
||||||
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.passwordExpirationPeriodDays').hasError('min')">
|
<mat-error
|
||||||
|
*ngIf="securitySettingsFormGroup.get('passwordPolicy.passwordExpirationPeriodDays').hasError('min')">
|
||||||
{{ 'admin.password-expiration-period-days-range' | translate }}
|
{{ 'admin.password-expiration-period-days-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="mat-block">
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>admin.password-reuse-frequency-days</mat-label>
|
<mat-label translate>admin.password-reuse-frequency-days</mat-label>
|
||||||
<input matInput type="number"
|
<input matInput type="number"
|
||||||
formControlName="passwordReuseFrequencyDays"
|
formControlName="passwordReuseFrequencyDays"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"/>
|
min="0"/>
|
||||||
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy.passwordReuseFrequencyDays').hasError('min')">
|
<mat-error
|
||||||
|
*ngIf="securitySettingsFormGroup.get('passwordPolicy.passwordReuseFrequencyDays').hasError('min')">
|
||||||
{{ 'admin.password-reuse-frequency-days-range' | translate }}
|
{{ 'admin.password-reuse-frequency-days-range' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-checkbox formControlName = "allowWhitespaces" >
|
</div>
|
||||||
|
<mat-checkbox formControlName="allowWhitespaces" style="margin-bottom: 16px">
|
||||||
<mat-label translate>admin.allow-whitespace</mat-label>
|
<mat-label translate>admin.allow-whitespace</mat-label>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</section>
|
</section>
|
||||||
</mat-expansion-panel>
|
</fieldset>
|
||||||
</mat-accordion>
|
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap" style="margin-top: 16px">
|
||||||
</div>
|
<button mat-button color="primary"
|
||||||
<div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap">
|
[disabled]="securitySettingsFormGroup.pristine"
|
||||||
|
(click)="discardSetting()"
|
||||||
|
type="button">{{'action.undo' | translate}}
|
||||||
|
</button>
|
||||||
<button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || securitySettingsFormGroup.invalid || !securitySettingsFormGroup.dirty"
|
<button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || securitySettingsFormGroup.invalid || !securitySettingsFormGroup.dirty"
|
||||||
type="submit">{{'action.save' | translate}}
|
type="submit">{{'action.save' | translate}}
|
||||||
</button>
|
</button>
|
||||||
@ -154,5 +158,94 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
<mat-card class="settings-card">
|
||||||
|
<mat-card-title>
|
||||||
|
<div fxLayout="row">
|
||||||
|
<span class="mat-headline" translate>admin.jwt.security-settings</span>
|
||||||
|
</div>
|
||||||
|
</mat-card-title>
|
||||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||||
|
</mat-progress-bar>
|
||||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||||
|
<mat-card-content style="padding-top: 16px;">
|
||||||
|
<form [formGroup]="jwtSecuritySettingsFormGroup" (ngSubmit)="saveJwtSettings()" autocomplete="off">
|
||||||
|
<fieldset [disabled]="isLoading$ | async">
|
||||||
|
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
|
<mat-label translate>admin.jwt.issuer-name</mat-label>
|
||||||
|
<input matInput required formControlName="tokenIssuer"/>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('tokenIssuer').hasError('required')">
|
||||||
|
{{ 'admin.jwt.issuer-name-required' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
|
<mat-label translate>admin.jwt.signings-key</mat-label>
|
||||||
|
<input matInput required formControlName="tokenSigningKey"/>
|
||||||
|
<button type="button"
|
||||||
|
matSuffix
|
||||||
|
mat-button
|
||||||
|
(click)="generateSigningKey()"
|
||||||
|
color="primary">
|
||||||
|
{{ 'admin.jwt.generate-key' | translate }}
|
||||||
|
</button>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('tokenSigningKey').hasError('required')">
|
||||||
|
{{ 'admin.jwt.signings-key-required' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('tokenSigningKey').hasError('base64')">
|
||||||
|
{{ 'admin.jwt.signings-key-base64' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
|
<mat-label translate>admin.jwt.expiration-time</mat-label>
|
||||||
|
<input matInput type="number" required
|
||||||
|
formControlName="tokenExpirationTime"
|
||||||
|
step="1"
|
||||||
|
min="0"/>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('tokenExpirationTime').hasError('required')">
|
||||||
|
{{ 'admin.jwt.expiration-time-required' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('tokenExpirationTime').hasError('pattern')">
|
||||||
|
{{ 'admin.jwt.expiration-time-pattern' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('tokenExpirationTime').hasError('min')">
|
||||||
|
{{ 'admin.jwt.expiration-time-min' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field fxFlex class="mat-block">
|
||||||
|
<mat-label translate>admin.jwt.refresh-expiration-time</mat-label>
|
||||||
|
<input matInput type="number" required
|
||||||
|
formControlName="refreshTokenExpTime"
|
||||||
|
step="1"
|
||||||
|
min="0"/>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('refreshTokenExpTime').hasError('required')">
|
||||||
|
{{ 'admin.jwt.refresh-expiration-time-required' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('refreshTokenExpTime').hasError('pattern')">
|
||||||
|
{{ 'admin.jwt.refresh-expiration-time-pattern' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('refreshTokenExpTime').hasError('min')">
|
||||||
|
{{ 'admin.jwt.refresh-expiration-time-min' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="jwtSecuritySettingsFormGroup.get('refreshTokenExpTime').hasError('lessToken')">
|
||||||
|
{{ 'admin.jwt.refresh-expiration-time-less-token' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap">
|
||||||
|
<button mat-button color="primary"
|
||||||
|
[disabled]="jwtSecuritySettingsFormGroup.pristine"
|
||||||
|
(click)="discardJwtSetting()"
|
||||||
|
type="button">{{'action.undo' | translate}}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary"
|
||||||
|
[disabled]="(isLoading$ | async) || jwtSecuritySettingsFormGroup.invalid || !jwtSecuritySettingsFormGroup.dirty"
|
||||||
|
type="submit">{{'action.save' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|||||||
@ -14,7 +14,26 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
:host {
|
:host {
|
||||||
.mat-accordion-container {
|
.mat-headline {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-card-title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-card-content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields-group {
|
||||||
|
padding: 8px 16px 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px groove rgba(0, 0, 0, .25);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
legend {
|
||||||
|
color: rgba(0, 0, 0, .7);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,40 +14,50 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
import { PageComponent } from '@shared/components/page.component';
|
import { PageComponent } from '@shared/components/page.component';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { SecuritySettings } from '@shared/models/settings.models';
|
import { JwtSettings, SecuritySettings } from '@shared/models/settings.models';
|
||||||
import { AdminService } from '@core/http/admin.service';
|
import { AdminService } from '@core/http/admin.service';
|
||||||
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
||||||
|
import { mergeMap, tap } from 'rxjs/operators';
|
||||||
|
import { randomAlphanumeric } from '@core/utils';
|
||||||
|
import { AuthService } from '@core/auth/auth.service';
|
||||||
|
import { DialogService } from '@core/services/dialog.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-security-settings',
|
selector: 'tb-security-settings',
|
||||||
templateUrl: './security-settings.component.html',
|
templateUrl: './security-settings.component.html',
|
||||||
styleUrls: ['./security-settings.component.scss', './settings-card.scss']
|
styleUrls: ['./security-settings.component.scss', './settings-card.scss']
|
||||||
})
|
})
|
||||||
export class SecuritySettingsComponent extends PageComponent implements OnInit, HasConfirmForm {
|
export class SecuritySettingsComponent extends PageComponent implements HasConfirmForm {
|
||||||
|
|
||||||
securitySettingsFormGroup: FormGroup;
|
securitySettingsFormGroup: FormGroup;
|
||||||
securitySettings: SecuritySettings;
|
jwtSecuritySettingsFormGroup: FormGroup;
|
||||||
|
|
||||||
|
private securitySettings: SecuritySettings;
|
||||||
|
private jwtSettings: JwtSettings;
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private adminService: AdminService,
|
private adminService: AdminService,
|
||||||
public fb: FormBuilder) {
|
private authService: AuthService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private fb: FormBuilder) {
|
||||||
super(store);
|
super(store);
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.buildSecuritySettingsForm();
|
this.buildSecuritySettingsForm();
|
||||||
|
this.buildJwtSecuritySettingsForm();
|
||||||
this.adminService.getSecuritySettings().subscribe(
|
this.adminService.getSecuritySettings().subscribe(
|
||||||
(securitySettings) => {
|
securitySettings => this.processSecuritySettings(securitySettings)
|
||||||
this.securitySettings = securitySettings;
|
);
|
||||||
this.securitySettingsFormGroup.reset(this.securitySettings);
|
this.adminService.getJwtSettings().subscribe(
|
||||||
}
|
jwtSettings => this.processJwtSettings(jwtSettings)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,18 +80,104 @@ export class SecuritySettingsComponent extends PageComponent implements OnInit,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
buildJwtSecuritySettingsForm() {
|
||||||
this.securitySettings = {...this.securitySettings, ...this.securitySettingsFormGroup.value};
|
this.jwtSecuritySettingsFormGroup = this.fb.group({
|
||||||
this.adminService.saveSecuritySettings(this.securitySettings).subscribe(
|
tokenIssuer: ['', Validators.required],
|
||||||
(securitySettings) => {
|
tokenSigningKey: ['', [Validators.required, this.base64Format]],
|
||||||
this.securitySettings = securitySettings;
|
tokenExpirationTime: [0, [Validators.required, Validators.pattern('[0-9]*'), Validators.min(60)]],
|
||||||
this.securitySettingsFormGroup.reset(this.securitySettings);
|
refreshTokenExpTime: [0, [Validators.required, Validators.pattern('[0-9]*'), Validators.min(900)]]
|
||||||
}
|
}, {validators: this.refreshTokenTimeGreatTokenTime.bind(this)});
|
||||||
|
this.jwtSecuritySettingsFormGroup.get('tokenExpirationTime').valueChanges.subscribe(
|
||||||
|
() => this.jwtSecuritySettingsFormGroup.get('refreshTokenExpTime').updateValueAndValidity({onlySelf: true})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
this.securitySettings = {...this.securitySettings, ...this.securitySettingsFormGroup.value};
|
||||||
|
this.adminService.saveSecuritySettings(this.securitySettings).subscribe(
|
||||||
|
securitySettings => this.processSecuritySettings(securitySettings)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveJwtSettings() {
|
||||||
|
const jwtFormSettings = this.jwtSecuritySettingsFormGroup.value;
|
||||||
|
this.confirmChangeJWTSettings().pipe(mergeMap(value => {
|
||||||
|
if (value) {
|
||||||
|
return this.adminService.saveJwtSettings(jwtFormSettings).pipe(
|
||||||
|
tap((data) => this.authService.setUserFromJwtToken(data.token, data.refreshToken, false)),
|
||||||
|
mergeMap(() => this.adminService.getJwtSettings()),
|
||||||
|
tap(jwtSettings => this.processJwtSettings(jwtSettings))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return of(null);
|
||||||
|
})).subscribe(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
discardSetting() {
|
||||||
|
this.securitySettingsFormGroup.reset(this.securitySettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
discardJwtSetting() {
|
||||||
|
this.jwtSecuritySettingsFormGroup.reset(this.jwtSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private confirmChangeJWTSettings(): Observable<boolean> {
|
||||||
|
if (this.jwtSecuritySettingsFormGroup.get('tokenIssuer').value !== (this.jwtSettings?.tokenIssuer || '') ||
|
||||||
|
this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').value !== (this.jwtSettings?.tokenSigningKey || '')) {
|
||||||
|
return this.dialogService.confirm(
|
||||||
|
this.translate.instant('admin.jwt.info-header'),
|
||||||
|
`<div style="max-width: 400px">${this.translate.instant('admin.jwt.info-message')}</div>`,
|
||||||
|
this.translate.instant('action.discard-changes'),
|
||||||
|
this.translate.instant('action.confirm')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return of(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSigningKey() {
|
||||||
|
this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').setValue(randomAlphanumeric(44));
|
||||||
|
if (this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').pristine) {
|
||||||
|
this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').markAsDirty();
|
||||||
|
this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').markAsTouched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private processSecuritySettings(securitySettings: SecuritySettings) {
|
||||||
|
this.securitySettings = securitySettings;
|
||||||
|
this.securitySettingsFormGroup.reset(this.securitySettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processJwtSettings(jwtSettings: JwtSettings) {
|
||||||
|
this.jwtSettings = jwtSettings;
|
||||||
|
this.jwtSecuritySettingsFormGroup.reset(jwtSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshTokenTimeGreatTokenTime(formGroup: FormGroup): { [key: string]: boolean } | null {
|
||||||
|
if (formGroup) {
|
||||||
|
const tokenTime = formGroup.value.tokenExpirationTime;
|
||||||
|
const refreshTokenTime = formGroup.value.refreshTokenExpTime;
|
||||||
|
if (tokenTime >= refreshTokenTime ) {
|
||||||
|
if (formGroup.get('refreshTokenExpTime').untouched) {
|
||||||
|
formGroup.get('refreshTokenExpTime').markAsTouched();
|
||||||
|
}
|
||||||
|
formGroup.get('refreshTokenExpTime').setErrors({lessToken: true});
|
||||||
|
return {lessToken: true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private base64Format(control: FormControl): { [key: string]: boolean } | null {
|
||||||
|
try {
|
||||||
|
const value = btoa(control.value);
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return {base64: true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
confirmForm(): FormGroup {
|
confirmForm(): FormGroup {
|
||||||
return this.securitySettingsFormGroup;
|
return this.securitySettingsFormGroup.dirty ? this.securitySettingsFormGroup : this.jwtSecuritySettingsFormGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<h2 mat-dialog-title>{{data.title}}</h2>
|
<h2 mat-dialog-title>{{data.title}}</h2>
|
||||||
<div mat-dialog-content [innerHTML]="data.message"></div>
|
<div mat-dialog-content [innerHTML]="data.message | safe: 'html'"></div>
|
||||||
<div mat-dialog-actions fxLayoutAlign="end center">
|
<div mat-dialog-actions fxLayoutAlign="end center">
|
||||||
<button mat-button color="primary" [mat-dialog-close]="false">{{data.cancel}}</button>
|
<button mat-button color="primary" [mat-dialog-close]="false">{{data.cancel}}</button>
|
||||||
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button>
|
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button>
|
||||||
|
|||||||
@ -63,6 +63,13 @@ export interface SecuritySettings {
|
|||||||
passwordPolicy: UserPasswordPolicy;
|
passwordPolicy: UserPasswordPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JwtSettings {
|
||||||
|
tokenIssuer: string;
|
||||||
|
tokenSigningKey: string;
|
||||||
|
tokenExpirationTime: number;
|
||||||
|
refreshTokenExpTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateMessage {
|
export interface UpdateMessage {
|
||||||
message: string;
|
message: string;
|
||||||
updateAvailable: boolean;
|
updateAvailable: boolean;
|
||||||
|
|||||||
@ -381,6 +381,26 @@
|
|||||||
"within-time": "Within time (sec)",
|
"within-time": "Within time (sec)",
|
||||||
"within-time-pattern": "Time must be a positive integer.",
|
"within-time-pattern": "Time must be a positive integer.",
|
||||||
"within-time-required": "Time is required."
|
"within-time-required": "Time is required."
|
||||||
|
},
|
||||||
|
"jwt": {
|
||||||
|
"security-settings": "JWT security settings",
|
||||||
|
"issuer-name": "Issuer name",
|
||||||
|
"issuer-name-required": "Issuer name is required.",
|
||||||
|
"signings-key": "Signing key",
|
||||||
|
"signings-key-required": "Signing key is required.",
|
||||||
|
"signings-key-base64": "Signing key must be base64 format.",
|
||||||
|
"expiration-time": "Token expiration time (sec)",
|
||||||
|
"expiration-time-required": "Token expiration time is required.",
|
||||||
|
"expiration-time-pattern": "Token expiration time be a positive integer.",
|
||||||
|
"expiration-time-min": "Minimum time is 60 seconds (1 minute).",
|
||||||
|
"refresh-expiration-time": "Refresh token expiration time",
|
||||||
|
"refresh-expiration-time-required": "Refresh token expiration time is required.",
|
||||||
|
"refresh-expiration-time-pattern": "Refresh token expiration time be a positive integer.",
|
||||||
|
"refresh-expiration-time-min": "Minimum time is 900 seconds (15 minute).",
|
||||||
|
"refresh-expiration-time-less-token": "Refresh token time must be greater token time.",
|
||||||
|
"generate-key": "Generate key",
|
||||||
|
"info-header": "All users will be to re-logined",
|
||||||
|
"info-message": "Change of the JWT Signing Key will cause all issued tokens to be invalid. All users will need to re-login. This will also affect scripts that use Rest API/Websockets."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alarm": {
|
"alarm": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user