Browse Source

UI: Add 2FA general setting form

pull/6235/head
Vladyslav_Prykhodko 4 years ago
parent
commit
e29be2bded
  1. 16
      ui-ngx/src/app/core/http/two-factor-authentication.service.ts
  2. 16
      ui-ngx/src/app/core/services/menu.service.ts
  3. 266
      ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html
  4. 21
      ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss
  5. 71
      ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts
  6. 22
      ui-ngx/src/app/shared/models/two-factor-auth.models.ts
  7. 26
      ui-ngx/src/assets/locale/locale.constant-en_US.json

16
ui-ngx/src/app/core/http/two-factor-authentication.service.ts

@ -1,3 +1,19 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';

16
ui-ngx/src/app/core/services/menu.service.ts

@ -364,7 +364,7 @@ export class MenuService {
name: 'admin.system-settings', name: 'admin.system-settings',
type: 'toggle', type: 'toggle',
path: '/settings', path: '/settings',
height: '80px', height: '120px',
icon: 'settings', icon: 'settings',
pages: [ pages: [
{ {
@ -374,6 +374,14 @@ export class MenuService {
path: '/settings/home', path: '/settings/home',
icon: 'settings_applications' icon: 'settings_applications'
}, },
{
id: guid(),
name: 'admin.2fa.2fa',
type: 'link',
path: '/settings/2fa',
icon: 'mdi:two-factor-authentication',
isMdiIcon: true
},
{ {
id: guid(), id: guid(),
name: 'resource.resources-library', name: 'resource.resources-library',
@ -510,6 +518,12 @@ export class MenuService {
icon: 'settings_applications', icon: 'settings_applications',
path: '/settings/home' path: '/settings/home'
}, },
{
name: 'admin.2fa.2fa',
icon: 'mdi:two-factor-authentication',
isMdiIcon: true,
path: '/settings/2fa'
},
{ {
name: 'resource.resources-library', name: 'resource.resources-library',
icon: 'folder', icon: 'folder',

266
ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html

@ -1,3 +1,20 @@
<!--
Copyright © 2016-2022 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div> <div>
<mat-card class="settings-card"> <mat-card class="settings-card">
<mat-card-title> <mat-card-title>
@ -11,131 +28,146 @@
</mat-progress-bar> </mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px;"> <mat-card-content style="padding-top: 16px;">
<form [formGroup]="twoFaFormGroup" (ngSubmit)="save()" fxLayout="column"> <form [formGroup]="twoFaFormGroup" (ngSubmit)="save()">
<mat-checkbox *ngIf="isTenantAdmin()" formControlName="useSystemTwoFactorAuthSettings" style="padding-bottom: 16px;"> <fieldset [disabled]="isLoading$ | async">
{{ 'admin.2fa.use-system-two-factor-auth-settings' | translate }} <mat-checkbox *ngIf="isTenantAdmin()" formControlName="useSystemTwoFactorAuthSettings" style="padding-bottom: 16px;">
</mat-checkbox> {{ 'admin.2fa.use-system-two-factor-auth-settings' | translate }}
<ng-container *ngIf="!isTenantAdmin() || twoFaFormGroup.get('useSystemTwoFactorAuthSettings').value"> </mat-checkbox>
<mat-form-field> <ng-container *ngIf="!isTenantAdmin() || !twoFaFormGroup.get('useSystemTwoFactorAuthSettings').value">
<mat-label translate>admin.2fa.total-allowed-time-for-verification</mat-label> <mat-form-field fxFlex class="mat-block">
<input matInput required formControlName="totalAllowedTimeForVerification" type="number" step="1" min="1"> <mat-label translate>admin.2fa.total-allowed-time-for-verification</mat-label>
<mat-error *ngIf="twoFaFormGroup.get('totalAllowedTimeForVerification').hasError('required')"> <input matInput required formControlName="totalAllowedTimeForVerification" type="number" step="1" min="1">
{{ 'admin.2fa.total-allowed-time-for-verification-required' | translate }} <mat-error *ngIf="twoFaFormGroup.get('totalAllowedTimeForVerification').hasError('required')">
</mat-error> {{ 'admin.2fa.total-allowed-time-for-verification-required' | translate }}
<mat-error *ngIf="twoFaFormGroup.get('totalAllowedTimeForVerification').hasError('pattern') </mat-error>
|| twoFaFormGroup.get('totalAllowedTimeForVerification').hasError('min')"> <mat-error *ngIf="twoFaFormGroup.get('totalAllowedTimeForVerification').hasError('pattern')
{{ 'admin.2fa.total-allowed-time-for-verification-pattern' | translate }} || twoFaFormGroup.get('totalAllowedTimeForVerification').hasError('min')">
</mat-error> {{ 'admin.2fa.total-allowed-time-for-verification-pattern' | translate }}
</mat-form-field> </mat-error>
<mat-form-field> </mat-form-field>
<mat-label translate>admin.2fa.max-verification-failures-before-user-lockout</mat-label> <mat-form-field fxFlex class="mat-block">
<input matInput required formControlName="maxVerificationFailuresBeforeUserLockout" type="number" step="1" min="0" max="65535"> <mat-label translate>admin.2fa.max-verification-failures-before-user-lockout</mat-label>
<mat-error *ngIf="twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('required')"> <input matInput required formControlName="maxVerificationFailuresBeforeUserLockout" type="number" step="1" min="0" max="65535">
{{ 'admin.2fa.max-verification-failures-before-user-lockout-required' | translate }} <mat-error *ngIf="twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('required')">
</mat-error> {{ 'admin.2fa.max-verification-failures-before-user-lockout-required' | translate }}
<mat-error *ngIf="twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('pattern') </mat-error>
|| twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('min') <mat-error *ngIf="twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('pattern')
|| twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('max')"> || twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('min')
{{ 'admin.2fa.max-verification-failures-before-user-lockout-pattern' | translate }} || twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('max')">
</mat-error> {{ 'admin.2fa.max-verification-failures-before-user-lockout-pattern' | translate }}
</mat-form-field> </mat-error>
<mat-form-field> </mat-form-field>
<mat-label translate>admin.2fa.verification-code-send-rate-limit</mat-label> <mat-form-field fxFlex class="mat-block">
<input matInput formControlName="verificationCodeSendRateLimit"> <mat-label translate>admin.2fa.verification-code-send-rate-limit</mat-label>
<mat-hint translate>admin.2fa.verification-code-send-rate-limit-hint</mat-hint> <input matInput formControlName="verificationCodeSendRateLimit" required>
<mat-error *ngIf="twoFaFormGroup.get('verificationCodeSendRateLimit').hasError('pattern')"> <mat-error *ngIf="twoFaFormGroup.get('verificationCodeSendRateLimit').hasError('required')">
{{ 'admin.2fa.verification-code-send-rate-limit-pattern' | translate }} {{ 'admin.2fa.verification-code-send-rate-limit-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> <mat-error *ngIf="twoFaFormGroup.get('verificationCodeSendRateLimit').hasError('pattern')">
<mat-form-field> {{ 'admin.2fa.verification-code-send-rate-limit-pattern' | translate }}
<mat-label translate>admin.2fa.verification-code-check-rate-limit</mat-label> </mat-error>
<input matInput formControlName="verificationCodeCheckRateLimit"> </mat-form-field>
<mat-hint translate>admin.2fa.verification-code-check-rate-limit-hint</mat-hint> <mat-form-field fxFlex class="mat-block">
<mat-error *ngIf="twoFaFormGroup.get('verificationCodeCheckRateLimit').hasError('pattern')"> <mat-label translate>admin.2fa.verification-code-check-rate-limit</mat-label>
{{ 'admin.2fa.verification-code-check-rate-limit-pattern' | translate }} <input matInput formControlName="verificationCodeCheckRateLimit" required>
</mat-error> <mat-error *ngIf="twoFaFormGroup.get('verificationCodeCheckRateLimit').hasError('required')">
</mat-form-field> {{ 'admin.2fa.verification-code-check-rate-limit-required' | translate }}
<div class="mat-h3">Providers</div> </mat-error>
<ng-container formArrayName="providers"> <mat-error *ngIf="twoFaFormGroup.get('verificationCodeCheckRateLimit').hasError('pattern')">
<div class="container"> {{ 'admin.2fa.verification-code-check-rate-limit-pattern' | translate }}
<mat-expansion-panel *ngFor="let provider of providersForm.controls; let j = index; trackBy: trackByParams" </mat-error>
class="registration-card mat-elevation-z0"> </mat-form-field>
<mat-expansion-panel-header> <div class="mat-h3" translate>admin.2fa.available-providers</div>
<mat-panel-title fxLayoutAlign="start center"> <ng-container formArrayName="providers">
{{ provider.value.providerType }} <div class="container">
</mat-panel-title> <mat-accordion multi>
<mat-panel-description fxLayoutAlign="end center"> <mat-expansion-panel *ngFor="let provider of providersForm.controls; let i = index">
<button mat-icon-button <mat-expansion-panel-header>
type="button" <mat-panel-title fxLayoutAlign="start center">
[disabled]="providersForm.controls.length < 2" {{ provider.value.providerType }}
(click)="removeProviders($event, j)" </mat-panel-title>
matTooltip="{{ 'admin.oauth2.delete-provider' | translate }}" <mat-panel-description fxLayoutAlign="end center">
matTooltipPosition="above"> <button mat-icon-button
<mat-icon>delete</mat-icon> type="button"
</button> (click)="removeProviders($event, i)"
</mat-panel-description> matTooltip="{{ 'admin.oauth2.delete-provider' | translate }}"
</mat-expansion-panel-header> matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<section [formGroupName]="j"> <section [formGroupName]="i">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<section fxFlex formGroupName="additionalInfo" fxLayout="row">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-provider</mat-label> <mat-label translate>admin.2fa.provider</mat-label>
<mat-select formControlName="providerName"> <mat-select formControlName="providerType">
<mat-option *ngFor="let provider of templateProvider" [value]="provider"> <mat-option *ngFor="let twoFactorAuthProviderType of twoFactorAuthProviderTypes"
{{ provider }} [value]="twoFactorAuthProviderType"
[disabled]="selectedTypes(twoFactorAuthProviderType[twoFactorAuthProviderType], i)">
{{ twoFactorAuthProviderType }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</section> <ng-container [ngSwitch]="provider.get('providerType').value">
<mat-form-field floatLabel="always" fxFlex class="mat-block"> <ng-container *ngSwitchCase="twoFactorAuthProviderType.TOTP">
<mat-label translate>admin.oauth2.allowed-platforms</mat-label> <mat-form-field fxFlex class="mat-block">
<mat-select formControlName="platforms" multiple placeholder="{{ 'admin.oauth2.all-platforms' | translate }}"> <mat-label translate>admin.2fa.issuer-name</mat-label>
<mat-option *ngFor="let platform of platformTypes" [value]="platform"> <input matInput formControlName="issuerName" required>
{{ platformTypeTranslations.get(platform) | translate }} <mat-error *ngIf="provider.get('issuerName').hasError('required')">
</mat-option> {{ "admin.2fa.issuer-name-required" | translate }}
</mat-select> </mat-error>
</mat-form-field> </mat-form-field>
</div> </ng-container>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> <div *ngSwitchCase="twoFactorAuthProviderType.SMS"
<mat-form-field fxFlex class="mat-block"> fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-label translate>admin.oauth2.client-id</mat-label> <mat-form-field fxFlex class="mat-block">
<input matInput formControlName="clientId" required> <mat-label translate>admin.2fa.verification-message-template</mat-label>
<mat-error *ngIf="registration.get('clientId').hasError('required')"> <input matInput formControlName="smsVerificationMessageTemplate" required>
{{ 'admin.oauth2.client-id-required' | translate }} <mat-error *ngIf="provider.get('smsVerificationMessageTemplate').hasError('required')">
</mat-error> {{ "admin.2fa.verification-message-template-required" | translate }}
<mat-error *ngIf="registration.get('clientId').hasError('maxlength')"> </mat-error>
{{ 'admin.oauth2.client-id-max-length' | translate }} <mat-error *ngIf="provider.get('smsVerificationMessageTemplate').hasError('pattern')">
</mat-error> {{ "admin.2fa.verification-message-template-pattern" | translate }}
</mat-form-field> </mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-secret</mat-label> <mat-label translate>admin.2fa.verification-code-lifetime</mat-label>
<input matInput formControlName="clientSecret" required> <input matInput formControlName="verificationCodeLifetime" type="number" step="1" min="1" required>
<mat-error *ngIf="registration.get('clientSecret').hasError('required')"> <mat-error *ngIf="provider.get('verificationCodeLifetime').hasError('required')">
{{ 'admin.oauth2.client-secret-required' | translate }} {{ "admin.2fa.verification-code-lifetime-required" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="registration.get('clientSecret').hasError('maxlength')"> <mat-error *ngIf="provider.get('verificationCodeLifetime').hasError('min') ||
{{ 'admin.oauth2.client-secret-max-length' | translate }} provider.get('verificationCodeLifetime').hasError('pattern')">
</mat-error> {{ "admin.2fa.verification-code-lifetime-pattern" | translate }}
</mat-form-field> </mat-error>
</div> </mat-form-field>
</div>
</ng-container>
</section>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>
</ng-container>
</section>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container> </ng-container>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
</ng-container> <button type="button" mat-raised-button color="primary"
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> [disabled]="twoFaFormGroup.get('useSystemTwoFactorAuthSettings').value
<button mat-button mat-raised-button color="primary" || providersForm.length == twoFactorAuthProviderTypes.length || (isLoading$ | async)"
[disabled]="(isLoading$ | async) || twoFaFormGroup.invalid || !twoFaFormGroup.dirty" (click)="addProvider()">
type="submit"> <mat-icon>add</mat-icon>
{{'action.save' | translate}} <span translate>action.add</span>
</button> </button>
</div> <button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async) || twoFaFormGroup.invalid || !twoFaFormGroup.dirty"
type="submit">
{{'action.save' | translate}}
</button>
</div>
</fieldset>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

21
ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
.container {
margin-bottom: 1em;
}
}

71
ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts

@ -1,10 +1,26 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
@ -12,11 +28,7 @@ import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentica
import { AuthState } from '@core/auth/auth.models'; import { AuthState } from '@core/auth/auth.models';
import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { import { TwoFactorAuthProviderType, TwoFactorAuthSettings } from '@shared/models/two-factor-auth.models';
TwoFactorAuthProviderType,
TwoFactorAuthSettings,
TwoFactorAuthSettingsForm
} from '@shared/models/two-factor-auth.models';
@Component({ @Component({
selector: 'tb-2fa-settings', selector: 'tb-2fa-settings',
@ -29,6 +41,8 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
private authUser = this.authState.authUser; private authUser = this.authState.authUser;
twoFaFormGroup: FormGroup; twoFaFormGroup: FormGroup;
twoFactorAuthProviderTypes = Object.keys(TwoFactorAuthProviderType);
twoFactorAuthProviderType = TwoFactorAuthProviderType;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -43,7 +57,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
ngOnInit() { ngOnInit() {
this.build2faSettingsForm(); this.build2faSettingsForm();
this.twoFaService.getTwoFaSettings().subscribe((setting) => { this.twoFaService.getTwoFaSettings().subscribe((setting) => {
console.log(this.formDataPreprocessing(setting)); this.initTwoFactorAuthForm(setting);
}); });
} }
@ -60,12 +74,19 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
} }
save() { save() {
const setting = this.twoFaFormGroup.value;
this.twoFaService.saveTwoFaSettings(setting).subscribe(
(twoFactorAuthSettings) => {
this.twoFaFormGroup.patchValue(twoFactorAuthSettings, {emitEvent: false});
this.twoFaFormGroup.markAsUntouched();
this.twoFaFormGroup.markAsPristine();
}
);
} }
private build2faSettingsForm(): void { private build2faSettingsForm(): void {
this.twoFaFormGroup = this.fb.group({ this.twoFaFormGroup = this.fb.group({
useSystemTwoFactorAuthSettings: [false], useSystemTwoFactorAuthSettings: [this.isTenantAdmin()],
maxVerificationFailuresBeforeUserLockout: [30, [ maxVerificationFailuresBeforeUserLockout: [30, [
Validators.required, Validators.required,
Validators.pattern(/^\d*$/), Validators.pattern(/^\d*$/),
@ -77,13 +98,20 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
Validators.min(1), Validators.min(1),
Validators.pattern(/^\d*$/) Validators.pattern(/^\d*$/)
]], ]],
verificationCodeCheckRateLimit: ['', Validators.pattern(/^[1-9]\d*:[1-9]\d*$/)], verificationCodeCheckRateLimit: ['3:900', [Validators.required, Validators.pattern(/^[1-9]\d*:[1-9]\d*$/)]],
verificationCodeSendRateLimit: ['', Validators.pattern(/^[1-9]\d*:[1-9]\d*$/)], verificationCodeSendRateLimit: ['1:60', [Validators.required, Validators.pattern(/^[1-9]\d*:[1-9]\d*$/)]],
providers: this.fb.array([]) providers: this.fb.array([])
}); });
} }
addProviders() { private initTwoFactorAuthForm(settings: TwoFactorAuthSettings) {
settings.providers.forEach(() => {
this.addProvider();
});
this.twoFaFormGroup.patchValue(settings, {emitEvent: false});
}
addProvider() {
const newProviders = this.fb.group({ const newProviders = this.fb.group({
providerType: [TwoFactorAuthProviderType.TOTP], providerType: [TwoFactorAuthProviderType.TOTP],
issuerName: ['', Validators.required], issuerName: ['', Validators.required],
@ -119,8 +147,9 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
}); });
if (this.providersForm.length) { if (this.providersForm.length) {
const selectProvidersType = this.providersForm.value[0].providerType; const selectProvidersType = this.providersForm.value[0].providerType;
if (selectProvidersType !== TwoFactorAuthProviderType.TOTP) { if (selectProvidersType === TwoFactorAuthProviderType.TOTP) {
newProviders.get('providerType').patchValue(TwoFactorAuthProviderType.SMS, {emitEvents: true}) newProviders.get('providerType').setValue(TwoFactorAuthProviderType.SMS);
newProviders.updateValueAndValidity();
} }
} }
this.providersForm.push(newProviders); this.providersForm.push(newProviders);
@ -140,16 +169,10 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
return this.twoFaFormGroup.get('providers') as FormArray; return this.twoFaFormGroup.get('providers') as FormArray;
} }
private formDataPreprocessing(data: TwoFactorAuthSettings): TwoFactorAuthSettingsForm { selectedTypes(type: TwoFactorAuthProviderType, index: number): boolean {
return data; const selectedProviderTypes: TwoFactorAuthProviderType[] = this.providersForm.value.map(providers => providers.providerType);
} selectedProviderTypes.splice(index, 1);
return selectedProviderTypes.includes(type);
private formDataPostprocessing(data: TwoFactorAuthSettingsForm): TwoFactorAuthSettings{
return data;
}
trackByParams(index: number): number {
return index;
} }
} }

22
ui-ngx/src/app/shared/models/two-factor-auth.models.ts

@ -1,3 +1,19 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
export interface TwoFactorAuthSettings { export interface TwoFactorAuthSettings {
maxVerificationFailuresBeforeUserLockout: number; maxVerificationFailuresBeforeUserLockout: number;
providers: Array<TwoFactorAuthProviderConfig>; providers: Array<TwoFactorAuthProviderConfig>;
@ -7,7 +23,7 @@ export interface TwoFactorAuthSettings {
verificationCodeSendRateLimit: string; verificationCodeSendRateLimit: string;
} }
export type TwoFactorAuthProviderConfig = Partial<TotpTwoFactorAuthProviderConfig | SmsTwoFactorAuthProviderConfig> export type TwoFactorAuthProviderConfig = Partial<TotpTwoFactorAuthProviderConfig | SmsTwoFactorAuthProviderConfig>;
export interface TotpTwoFactorAuthProviderConfig { export interface TotpTwoFactorAuthProviderConfig {
providerType: TwoFactorAuthProviderType; providerType: TwoFactorAuthProviderType;
@ -24,7 +40,3 @@ export enum TwoFactorAuthProviderType{
TOTP = 'TOTP', TOTP = 'TOTP',
SMS = 'SMS' SMS = 'SMS'
} }
export interface TwoFactorAuthSettingsForm extends TwoFactorAuthSettings {
}

26
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -255,19 +255,29 @@
}, },
"2fa": { "2fa": {
"2fa": "Two-factor authentication", "2fa": "Two-factor authentication",
"use-system-two-factor-auth-settings": "Use system two factor auth settings", "available-providers": "Available providers:",
"total-allowed-time-for-verification": "Total allowed time for verification", "issuer-name": "Issuer name",
"total-allowed-time-for-verification-required": "Total allowed time is required.", "issuer-name-required": "Issuer name is required.",
"total-allowed-time-for-verification-pattern": "Total allowed time must be a positive integer.",
"max-verification-failures-before-user-lockout": "Max verification failures before user lockout", "max-verification-failures-before-user-lockout": "Max verification failures before user lockout",
"max-verification-failures-before-user-lockout-required": "Max verification failures is required.",
"max-verification-failures-before-user-lockout-pattern": "Max verification failures must be a positive integer.", "max-verification-failures-before-user-lockout-pattern": "Max verification failures must be a positive integer.",
"max-verification-failures-before-user-lockout-required": "Max verification failures is required.",
"provider": "Provider",
"total-allowed-time-for-verification": "Total allowed time for verification",
"total-allowed-time-for-verification-pattern": "Total allowed time must be a positive integer.",
"total-allowed-time-for-verification-required": "Total allowed time is required.",
"use-system-two-factor-auth-settings": "Use system two factor auth settings",
"verification-code-check-rate-limit": "Verification code check rate limit", "verification-code-check-rate-limit": "Verification code check rate limit",
"verification-code-check-rate-limit-hint": "If empty field, the limit not be apply",
"verification-code-check-rate-limit-pattern": "Verification code check limit has invalid format", "verification-code-check-rate-limit-pattern": "Verification code check limit has invalid format",
"verification-code-check-rate-limit-required": "Verification code check rate limit is required.",
"verification-code-lifetime": "Verification code lifetime",
"verification-code-lifetime-pattern": "Verification code lifetime must be a positive integer.",
"verification-code-lifetime-required": "Verification code lifetime is required.",
"verification-code-send-rate-limit": "Verification code send rate limit", "verification-code-send-rate-limit": "Verification code send rate limit",
"verification-code-send-rate-limit-hint": "If empty field, the limit not be apply", "verification-code-send-rate-limit-pattern": "Verification code send limit has invalid format",
"verification-code-send-rate-limit-pattern": "Verification code send limit has invalid format" "verification-code-send-rate-limit-required": "Verification code send rate limit is required.",
"verification-message-template": "Verification message template",
"verification-message-template-pattern": "Verification message need to contains pattern: ${verificationCode}",
"verification-message-template-required": "Verification message template is required."
} }
}, },
"alarm": { "alarm": {

Loading…
Cancel
Save