thingsboard/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html
ArtemDzhereleiko b84d818b28 UI: Enforce 2FA
2025-09-23 16:53:19 +03:00

256 lines
16 KiB
HTML

<!--
Copyright © 2016-2025 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>
<mat-card appearance="outlined" class="settings-card">
<mat-card-header>
<mat-card-title>
<span class="mat-headline-5" translate>admin.2fa.2fa</span>
</mat-card-title>
<span class="flex-1"></span>
<div tb-help="twoFactorAuthentication"></div>
</mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content>
<form [formGroup]="twoFaFormGroup" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings no-padding-bottom">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-center justify-start"
(mousedown)="toggleExtensionPanel($event, 0, twoFaFormGroup.get('enforceTwoFa').value)"
formControlName="enforceTwoFa">
{{ 'admin.2fa.force-2fa' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section class="tb-form-panel no-padding no-border" formGroupName="enforcedUsersFilter">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>admin.2fa.enforce-for</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of notificationTargetConfigTypes" [value]="type">
{{ notificationTargetConfigTypeInfoMap.get(type).name | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<section class="tb-form-panel no-padding no-border" *ngIf="twoFaFormGroup.get('enforcedUsersFilter.type').value === notificationTargetConfigType.TENANT_ADMINISTRATORS">
<div class="flex flex-1 items-center justify-center">
<tb-toggle-select class="tb-notification-tenant-group" appearance="fill"
formControlName="filterByTenants">
<tb-toggle-option [value]="true">{{ 'tenant.tenant' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="false">{{ 'tenant-profile.tenant-profile' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-container *ngIf="twoFaFormGroup.get('enforcedUsersFilter.filterByTenants').value; else tenantProfiles">
<tb-entity-list
formControlName="tenantsIds"
subscriptSizing="dynamic"
appearance="outline"
labelText="{{ 'tenant.tenants' | translate }}"
placeholderText="{{ 'tenant.tenants' | translate }}"
hint="{{ 'notification.tenants-list-rule-hint' | translate }}"
[entityType]="entityType.TENANT">
</tb-entity-list>
</ng-container>
<ng-template #tenantProfiles>
<tb-entity-list
formControlName="tenantProfilesIds"
subscriptSizing="dynamic"
appearance="outline"
labelText="{{ 'tenant-profile.tenant-profiles' | translate }}"
placeholderText="{{ 'tenant-profile.tenant-profiles' | translate }}"
hint="{{ 'notification.tenant-profiles-list-rule-hint' | translate }}"
[entityType]="entityType.TENANT_PROFILE">
</tb-entity-list>
</ng-template>
</section>
</section>
</ng-template>
</mat-expansion-panel>
</div>
<section class="tb-form-panel stroked" formArrayName="providers">
<div class="tb-form-panel-title" translate>admin.2fa.available-providers</div>
<ng-container *ngFor="let provider of providersForm.controls; let i = index; trackBy: trackByElement">
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [formGroupName]="i">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-center justify-start"
(mousedown)="toggleExtensionPanel($event, i, provider.get('enable').value)"
formControlName="enable">
{{ twoFactorAuthProvidersData.get(provider.value.providerType).name | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<ng-container [ngSwitch]="provider.get('providerType').value">
<ng-container *ngSwitchCase="twoFactorAuthProviderType.TOTP">
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>admin.2fa.issuer-name</mat-label>
<input matInput formControlName="issuerName" required>
<mat-error *ngIf="provider.get('issuerName').hasError('required') ||
provider.get('issuerName').hasError('pattern')">
{{ "admin.2fa.issuer-name-required" | translate }}
</mat-error>
</mat-form-field>
</ng-container>
<div *ngSwitchCase="twoFactorAuthProviderType.SMS"
>
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>admin.2fa.verification-message-template</mat-label>
<input matInput formControlName="smsVerificationMessageTemplate" required>
<mat-error *ngIf="provider.get('smsVerificationMessageTemplate').hasError('required')">
{{ "admin.2fa.verification-message-template-required" | translate }}
</mat-error>
<mat-error *ngIf="provider.get('smsVerificationMessageTemplate').hasError('pattern')">
{{ "admin.2fa.verification-message-template-pattern" | translate }}
</mat-error>
</mat-form-field>
<tb-time-unit-input
appearance="outline"
subscriptSizing="dynamic"
required
labelText="{{ 'admin.2fa.verification-code-lifetime' | translate }}"
requiredText="{{ 'admin.2fa.verification-code-lifetime-required' | translate }}"
minErrorText="{{ 'admin.2fa.verification-code-lifetime-pattern' | translate }}"
[minTime]="1"
formControlName="verificationCodeLifetime">
</tb-time-unit-input>
</div>
<div *ngSwitchCase="twoFactorAuthProviderType.EMAIL">
<tb-time-unit-input
appearance="outline"
subscriptSizing="dynamic"
required
labelText="{{ 'admin.2fa.verification-code-lifetime' | translate }}"
requiredText="{{ 'admin.2fa.verification-code-lifetime-required' | translate }}"
minErrorText="{{ 'admin.2fa.verification-code-lifetime-pattern' | translate }}"
[minTime]="1"
formControlName="verificationCodeLifetime">
</tb-time-unit-input>
</div>
<div *ngSwitchCase="twoFactorAuthProviderType.BACKUP_CODE">
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>admin.2fa.number-of-codes</mat-label>
<input matInput formControlName="codesQuantity" type="number" step="1" min="1" required>
<mat-error *ngIf="provider.get('codesQuantity').hasError('required')">
{{ "admin.2fa.number-of-codes-required" | translate }}
</mat-error>
<mat-error *ngIf="provider.get('codesQuantity').hasError('min') ||
provider.get('codesQuantity').hasError('pattern')">
{{ "admin.2fa.number-of-codes-pattern" | translate }}
</mat-error>
</mat-form-field>
</div>
</ng-container>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>
</section>
<section class="tb-form-panel stroked mb-4">
<div class="tb-form-panel-title" translate>admin.2fa.verification-limitations</div>
<div class="tb-form-panel no-gap no-border no-padding">
<div class="input-row flex flex-col">
<tb-time-unit-input
appearance="outline"
required
labelText="{{ 'admin.2fa.total-allowed-time-for-verification' | translate }}"
requiredText="{{ 'admin.2fa.total-allowed-time-for-verification-required' | translate }}"
minErrorText="{{ 'admin.2fa.total-allowed-time-for-verification-pattern' | translate }}"
[minTime]="60"
formControlName="totalAllowedTimeForVerification">
</tb-time-unit-input>
<tb-time-unit-input
appearance="outline"
required
labelText="{{ 'admin.2fa.retry-verification-code-period' | translate }}"
requiredText="{{ 'admin.2fa.retry-verification-code-period-required' | translate }}"
minErrorText="{{ 'admin.2fa.retry-verification-code-period-pattern' | translate }}"
[minTime]="5"
formControlName="minVerificationCodeSendPeriod">
</tb-time-unit-input>
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>admin.2fa.max-verification-failures-before-user-lockout</mat-label>
<input matInput formControlName="maxVerificationFailuresBeforeUserLockout" type="number" step="1" min="0" max="65535">
<mat-error *ngIf="twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('pattern')
|| twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('min')
|| twoFaFormGroup.get('maxVerificationFailuresBeforeUserLockout').hasError('max')">
{{ 'admin.2fa.max-verification-failures-before-user-lockout-pattern' | translate }}
</mat-error>
</mat-form-field>
</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-center justify-start" (mousedown)="toggleExtensionPanel($event, providersForm.length, twoFaFormGroup.get('verificationCodeCheckRateLimitEnable').value)"
formControlName="verificationCodeCheckRateLimitEnable">
{{ 'admin.2fa.verification-code-check-rate-limit' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="flex flex-col">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>admin.2fa.number-of-checking-attempts</mat-label>
<input matInput formControlName="verificationCodeCheckRateLimitNumber" required type="number" step="1" min="1">
<mat-error *ngIf="twoFaFormGroup.get('verificationCodeCheckRateLimitNumber').hasError('required')">
{{ 'admin.2fa.number-of-checking-attempts-required' | translate }}
</mat-error>
<mat-error *ngIf="twoFaFormGroup.get('verificationCodeCheckRateLimitNumber').hasError('pattern')
|| twoFaFormGroup.get('verificationCodeCheckRateLimitNumber').hasError('min')">
{{ 'admin.2fa.number-of-checking-attempts-pattern' | translate }}
</mat-error>
</mat-form-field>
<tb-time-unit-input
appearance="outline"
subscriptSizing="dynamic"
required
labelText="{{ 'admin.2fa.within-time' | translate }}"
requiredText="{{ 'admin.2fa.within-time-required' | translate }}"
minErrorText="{{ 'admin.2fa.within-time-pattern' | translate }}"
[minTime]="1"
formControlName="verificationCodeCheckRateLimitTime">
</tb-time-unit-input>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</div>
</section>
</div>
<div class="flex flex-row items-center justify-end gap-2">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async) || twoFaFormGroup.invalid || !twoFaFormGroup.dirty"
type="submit">
{{'action.save' | translate}}
</button>
</div>
</fieldset>
</form>
</mat-card-content>
</mat-card>
</div>