diff --git a/ui-ngx/src/app/core/http/two-factor-authentication.service.ts b/ui-ngx/src/app/core/http/two-factor-authentication.service.ts
index d608d3b362..230eddd208 100644
--- a/ui-ngx/src/app/core/http/two-factor-authentication.service.ts
+++ b/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 { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts
index 294513c782..15ab552d53 100644
--- a/ui-ngx/src/app/core/services/menu.service.ts
+++ b/ui-ngx/src/app/core/services/menu.service.ts
@@ -364,7 +364,7 @@ export class MenuService {
name: 'admin.system-settings',
type: 'toggle',
path: '/settings',
- height: '80px',
+ height: '120px',
icon: 'settings',
pages: [
{
@@ -374,6 +374,14 @@ export class MenuService {
path: '/settings/home',
icon: 'settings_applications'
},
+ {
+ id: guid(),
+ name: 'admin.2fa.2fa',
+ type: 'link',
+ path: '/settings/2fa',
+ icon: 'mdi:two-factor-authentication',
+ isMdiIcon: true
+ },
{
id: guid(),
name: 'resource.resources-library',
@@ -510,6 +518,12 @@ export class MenuService {
icon: 'settings_applications',
path: '/settings/home'
},
+ {
+ name: 'admin.2fa.2fa',
+ icon: 'mdi:two-factor-authentication',
+ isMdiIcon: true,
+ path: '/settings/2fa'
+ },
{
name: 'resource.resources-library',
icon: 'folder',
diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html
index 9a507e45f2..c92fe6de74 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html
+++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html
@@ -1,3 +1,20 @@
+
@@ -11,131 +28,146 @@
-
diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss
index e69de29bb2..8fb412f648 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss
+++ b/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;
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts
index 1995605786..992c1c8ae4 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts
+++ b/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 { PageComponent } from '@shared/components/page.component';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
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 { TranslateService } from '@ngx-translate/core';
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 { getCurrentAuthState } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
-import {
- TwoFactorAuthProviderType,
- TwoFactorAuthSettings,
- TwoFactorAuthSettingsForm
-} from '@shared/models/two-factor-auth.models';
+import { TwoFactorAuthProviderType, TwoFactorAuthSettings } from '@shared/models/two-factor-auth.models';
@Component({
selector: 'tb-2fa-settings',
@@ -29,6 +41,8 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
private authUser = this.authState.authUser;
twoFaFormGroup: FormGroup;
+ twoFactorAuthProviderTypes = Object.keys(TwoFactorAuthProviderType);
+ twoFactorAuthProviderType = TwoFactorAuthProviderType;
constructor(protected store: Store
,
private route: ActivatedRoute,
@@ -43,7 +57,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
ngOnInit() {
this.build2faSettingsForm();
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() {
-
+ 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 {
this.twoFaFormGroup = this.fb.group({
- useSystemTwoFactorAuthSettings: [false],
+ useSystemTwoFactorAuthSettings: [this.isTenantAdmin()],
maxVerificationFailuresBeforeUserLockout: [30, [
Validators.required,
Validators.pattern(/^\d*$/),
@@ -77,13 +98,20 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
Validators.min(1),
Validators.pattern(/^\d*$/)
]],
- verificationCodeCheckRateLimit: ['', Validators.pattern(/^[1-9]\d*:[1-9]\d*$/)],
- verificationCodeSendRateLimit: ['', Validators.pattern(/^[1-9]\d*:[1-9]\d*$/)],
+ verificationCodeCheckRateLimit: ['3:900', [Validators.required, 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([])
});
}
- addProviders() {
+ private initTwoFactorAuthForm(settings: TwoFactorAuthSettings) {
+ settings.providers.forEach(() => {
+ this.addProvider();
+ });
+ this.twoFaFormGroup.patchValue(settings, {emitEvent: false});
+ }
+
+ addProvider() {
const newProviders = this.fb.group({
providerType: [TwoFactorAuthProviderType.TOTP],
issuerName: ['', Validators.required],
@@ -119,8 +147,9 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
});
if (this.providersForm.length) {
const selectProvidersType = this.providersForm.value[0].providerType;
- if (selectProvidersType !== TwoFactorAuthProviderType.TOTP) {
- newProviders.get('providerType').patchValue(TwoFactorAuthProviderType.SMS, {emitEvents: true})
+ if (selectProvidersType === TwoFactorAuthProviderType.TOTP) {
+ newProviders.get('providerType').setValue(TwoFactorAuthProviderType.SMS);
+ newProviders.updateValueAndValidity();
}
}
this.providersForm.push(newProviders);
@@ -140,16 +169,10 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
return this.twoFaFormGroup.get('providers') as FormArray;
}
- private formDataPreprocessing(data: TwoFactorAuthSettings): TwoFactorAuthSettingsForm {
- return data;
- }
-
- private formDataPostprocessing(data: TwoFactorAuthSettingsForm): TwoFactorAuthSettings{
- return data;
- }
-
- trackByParams(index: number): number {
- return index;
+ selectedTypes(type: TwoFactorAuthProviderType, index: number): boolean {
+ const selectedProviderTypes: TwoFactorAuthProviderType[] = this.providersForm.value.map(providers => providers.providerType);
+ selectedProviderTypes.splice(index, 1);
+ return selectedProviderTypes.includes(type);
}
}
diff --git a/ui-ngx/src/app/shared/models/two-factor-auth.models.ts b/ui-ngx/src/app/shared/models/two-factor-auth.models.ts
index b2c32e6156..ab64143a66 100644
--- a/ui-ngx/src/app/shared/models/two-factor-auth.models.ts
+++ b/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 {
maxVerificationFailuresBeforeUserLockout: number;
providers: Array;
@@ -7,7 +23,7 @@ export interface TwoFactorAuthSettings {
verificationCodeSendRateLimit: string;
}
-export type TwoFactorAuthProviderConfig = Partial
+export type TwoFactorAuthProviderConfig = Partial;
export interface TotpTwoFactorAuthProviderConfig {
providerType: TwoFactorAuthProviderType;
@@ -24,7 +40,3 @@ export enum TwoFactorAuthProviderType{
TOTP = 'TOTP',
SMS = 'SMS'
}
-
-export interface TwoFactorAuthSettingsForm extends TwoFactorAuthSettings {
-
-}
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 642592b669..89b9bc5d2d 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -255,19 +255,29 @@
},
"2fa": {
"2fa": "Two-factor authentication",
- "use-system-two-factor-auth-settings": "Use system two factor auth settings",
- "total-allowed-time-for-verification": "Total allowed time for verification",
- "total-allowed-time-for-verification-required": "Total allowed time is required.",
- "total-allowed-time-for-verification-pattern": "Total allowed time must be a positive integer.",
+ "available-providers": "Available providers:",
+ "issuer-name": "Issuer name",
+ "issuer-name-required": "Issuer name is required.",
"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-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-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-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-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": {