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 @@
-
- - {{ 'admin.2fa.use-system-two-factor-auth-settings' | translate }} - - - - admin.2fa.total-allowed-time-for-verification - - - {{ 'admin.2fa.total-allowed-time-for-verification-required' | translate }} - - - {{ 'admin.2fa.total-allowed-time-for-verification-pattern' | translate }} - - - - admin.2fa.max-verification-failures-before-user-lockout - - - {{ 'admin.2fa.max-verification-failures-before-user-lockout-required' | translate }} - - - {{ 'admin.2fa.max-verification-failures-before-user-lockout-pattern' | translate }} - - - - admin.2fa.verification-code-send-rate-limit - - admin.2fa.verification-code-send-rate-limit-hint - - {{ 'admin.2fa.verification-code-send-rate-limit-pattern' | translate }} - - - - admin.2fa.verification-code-check-rate-limit - - admin.2fa.verification-code-check-rate-limit-hint - - {{ 'admin.2fa.verification-code-check-rate-limit-pattern' | translate }} - - -
Providers
- -
- - - - {{ provider.value.providerType }} - - - - - + +
+ + {{ 'admin.2fa.use-system-two-factor-auth-settings' | translate }} + + + + admin.2fa.total-allowed-time-for-verification + + + {{ 'admin.2fa.total-allowed-time-for-verification-required' | translate }} + + + {{ 'admin.2fa.total-allowed-time-for-verification-pattern' | translate }} + + + + admin.2fa.max-verification-failures-before-user-lockout + + + {{ 'admin.2fa.max-verification-failures-before-user-lockout-required' | translate }} + + + {{ 'admin.2fa.max-verification-failures-before-user-lockout-pattern' | translate }} + + + + admin.2fa.verification-code-send-rate-limit + + + {{ 'admin.2fa.verification-code-send-rate-limit-required' | translate }} + + + {{ 'admin.2fa.verification-code-send-rate-limit-pattern' | translate }} + + + + admin.2fa.verification-code-check-rate-limit + + + {{ 'admin.2fa.verification-code-check-rate-limit-required' | translate }} + + + {{ 'admin.2fa.verification-code-check-rate-limit-pattern' | translate }} + + +
admin.2fa.available-providers
+ +
+ + + + + {{ provider.value.providerType }} + + + + + - -
-
-
+ +
- admin.oauth2.login-provider - - - {{ provider }} + admin.2fa.provider + + + {{ twoFactorAuthProviderType }} + + + + admin.2fa.issuer-name + + + {{ "admin.2fa.issuer-name-required" | translate }} + + + +
+ + admin.2fa.verification-message-template + + + {{ "admin.2fa.verification-message-template-required" | translate }} + + + {{ "admin.2fa.verification-message-template-pattern" | translate }} + + + + + admin.2fa.verification-code-lifetime + + + {{ "admin.2fa.verification-code-lifetime-required" | translate }} + + + {{ "admin.2fa.verification-code-lifetime-pattern" | translate }} + + +
+
- - admin.oauth2.allowed-platforms - - - {{ platformTypeTranslations.get(platform) | translate }} - - - -
-
- - admin.oauth2.client-id - - - {{ 'admin.oauth2.client-id-required' | translate }} - - - {{ 'admin.oauth2.client-id-max-length' | translate }} - - + + + +
+ - - admin.oauth2.client-secret - - - {{ 'admin.oauth2.client-secret-required' | translate }} - - - {{ 'admin.oauth2.client-secret-max-length' | translate }} - - -
- - - - -
- -
-
- -
+
+ + +
+
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": {