30 changed files with 1088 additions and 626 deletions
@ -1,104 +0,0 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2>Enable email authenticator</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="closeDialog()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content style="padding: 0"> |
|||
<mat-stepper labelPosition="bottom" linear #stepper> |
|||
<ng-template matStepperIcon="edit"> |
|||
<mat-icon>done</mat-icon> |
|||
</ng-template> |
|||
<mat-step [stepControl]="emailConfigForm"> |
|||
<ng-template matStepLabel>Add email</ng-template> |
|||
<form [formGroup]="emailConfigForm" style="display: block"> |
|||
<mat-form-field class="mat-block" appearance="fill" style="max-width: 250px;"> |
|||
<mat-label translate>user.email</mat-label> |
|||
<input matInput formControlName="email" required/> |
|||
<mat-error *ngIf="emailConfigForm.get('email').hasError('required')"> |
|||
{{ 'user.email-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="emailConfigForm.get('email').hasError('email')"> |
|||
{{ 'user.invalid-email-format' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="false"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || emailConfigForm.invalid" |
|||
(click)="nextStep()"> |
|||
Send code |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step [stepControl]="emailVerificationForm"> |
|||
<ng-template matStepLabel>Authentication verification</ng-template> |
|||
<form [formGroup]="emailVerificationForm" style="display: block"> |
|||
<p class="mat-body-strong">Enter the 6-digit code here</p> |
|||
<p class="mat-body" style="max-width: 480px;">Enter the 6 digit verification code we just sent to {{ emailConfigForm.get('email').value }}.</p> |
|||
<mat-form-field class="mat-block" appearance="fill" style="max-width: 200px;"> |
|||
<mat-label>6-digit code</mat-label> |
|||
<input matInput formControlName="verificationCode" required maxlength="6" type="text" pattern="\d*"> |
|||
</mat-form-field> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="false"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || emailVerificationForm.invalid" |
|||
(click)="nextStep()"> |
|||
Activate |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step> |
|||
<ng-template matStepLabel matStepperIcon="done">Two-factor authentication activated</ng-template> |
|||
<div> |
|||
<h2 class="mat-h2">Email authentication enabled</h2> |
|||
<p class="mat-body" style="max-width: 480px;">If we notice an attempted login from a device or browser we don’t recognize, you will be prompted to enter the security code that will be sent to your email address.</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="true"> |
|||
Done |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
</mat-stepper> |
|||
</div> |
|||
@ -1,105 +0,0 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2>Enable SMS authenticator</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="closeDialog()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content style="padding: 0"> |
|||
<mat-stepper labelPosition="bottom" linear #stepper> |
|||
<ng-template matStepperIcon="edit"> |
|||
<mat-icon>done</mat-icon> |
|||
</ng-template> |
|||
<mat-step [stepControl]="smsConfigForm"> |
|||
<ng-template matStepLabel>Add phone number</ng-template> |
|||
<form [formGroup]="smsConfigForm" style="display: block"> |
|||
<p class="mat-body-1">Enter the phone number including the area code.</p> |
|||
<mat-form-field class="mat-block" appearance="fill" style="max-width: 250px;"> |
|||
<mat-label>Phone number</mat-label> |
|||
<input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="phone"> |
|||
<mat-error *ngIf="smsConfigForm.get('phone').hasError('required')"> |
|||
{{ 'admin.number-to-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="smsConfigForm.get('phone').hasError('pattern')"> |
|||
{{ 'admin.phone-number-pattern' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="false"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || smsConfigForm.invalid" |
|||
(click)="nextStep()"> |
|||
Send code |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step [stepControl]="smsVerificationForm"> |
|||
<ng-template matStepLabel>Authentication verification</ng-template> |
|||
<form [formGroup]="smsVerificationForm" style="display: block"> |
|||
<p class="mat-body-strong">Enter the 6-digit code here</p> |
|||
<p class="mat-body" style="max-width: 480px;">Enter the 6 digit verification code we just sent to {{ smsConfigForm.get('phone').value }}.</p> |
|||
<mat-form-field class="mat-block" appearance="fill" style="max-width: 200px;"> |
|||
<mat-label>6-digit code</mat-label> |
|||
<input matInput formControlName="verificationCode" required maxlength="6" type="text" pattern="\d*"> |
|||
</mat-form-field> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="false"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || smsVerificationForm.invalid" |
|||
(click)="nextStep()"> |
|||
Activate |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step> |
|||
<ng-template matStepLabel matStepperIcon="done">Two-factor authentication activated</ng-template> |
|||
<div> |
|||
<h2 class="mat-h2">SMS authentication enabled</h2> |
|||
<p class="mat-body" style="max-width: 480px;">If we notice an attempted login from a device or browser we don’t recognize, you will be prompted to enter the security code that will be sent to the phone number.</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="true"> |
|||
Done |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
</mat-stepper> |
|||
</div> |
|||
@ -1,15 +0,0 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
@ -1,97 +0,0 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2>Enable authenticator app</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="closeDialog()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content style="padding: 0"> |
|||
<mat-stepper labelPosition="bottom" linear #stepper> |
|||
<ng-template matStepperIcon="edit"> |
|||
<mat-icon>done</mat-icon> |
|||
</ng-template> |
|||
<mat-step> |
|||
<ng-template matStepLabel>Get app</ng-template> |
|||
<div> |
|||
<p class="mat-body-1">You'll need to use a verification app such as Google Authenticator, Authy, or Duo. Install from your app store</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="false"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
matStepperNext> |
|||
Next |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
<mat-step [stepControl]="totpConfigForm"> |
|||
<ng-template matStepLabel>Authentication verification</ng-template> |
|||
<form [formGroup]="totpConfigForm" style="display: block"> |
|||
<p class="mat-body-strong">1. Scan this QR code with your verification app</p> |
|||
<p class="mat-body" style="margin-bottom: 8px">Once your app reads the QR code, you'll get a 6-digit code.</p> |
|||
<canvas fxFlex #canvas [ngStyle]="{display: totpAuthURL ? 'block' : 'none'}"></canvas> |
|||
<p class="mat-body-strong">2. Enter the 6-digit code here</p> |
|||
<p class="mat-body" style="max-width: 480px;">Enter the code from the app below. Once connected, we'll remember your phone so you can use it each time you log in.</p> |
|||
<mat-form-field class="mat-block" appearance="fill" style="max-width: 200px;"> |
|||
<mat-label>6-digit code</mat-label> |
|||
<input matInput formControlName="verificationCode" required maxlength="6" type="text" pattern="\d*"> |
|||
</mat-form-field> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="false"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || totpConfigForm.invalid" |
|||
(click)="onSaveConfig()"> |
|||
Next |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step> |
|||
<ng-template matStepLabel matStepperIcon="done">Two-factor authentication activated</ng-template> |
|||
<div> |
|||
<h2 class="mat-h2">Success</h2> |
|||
<p class="mat-body" style="max-width: 480px;">The next time you login from an unrecognized browser or device, you will need to provide a two-factor authentication code.</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
[mat-dialog-close]="true"> |
|||
Done |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
</mat-stepper> |
|||
</div> |
|||
@ -1,15 +0,0 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
@ -0,0 +1,77 @@ |
|||
/** |
|||
* 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{ |
|||
.mat-toolbar > h2 { |
|||
font-weight: 400; |
|||
letter-spacing: 0.25px; |
|||
} |
|||
|
|||
form { |
|||
display: block; |
|||
} |
|||
|
|||
.mat-body-1 { |
|||
margin-bottom: 0; |
|||
letter-spacing: 0.25px; |
|||
|
|||
&:not(:first-of-type) { |
|||
margin: 0 0 8px; |
|||
} |
|||
} |
|||
|
|||
.input-container { |
|||
max-width: 290px; |
|||
} |
|||
|
|||
.code-container { |
|||
max-width: 170px; |
|||
} |
|||
|
|||
.result-title { |
|||
font: 500 18px / 24px Roboto, "Helvetica Neue", sans-serif; |
|||
letter-spacing: 0.1px; |
|||
margin: 8px 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.result-description { |
|||
text-align: center; |
|||
margin: 0 0 16px; |
|||
letter-spacing: 0.25px; |
|||
max-width: 500px; |
|||
} |
|||
|
|||
.step-description { |
|||
max-width: 450px; |
|||
&.input { |
|||
margin: 12px 0 0; |
|||
} |
|||
} |
|||
|
|||
.qr-code-description { |
|||
text-align: center; |
|||
max-width: 180px; |
|||
letter-spacing: 0.25px; |
|||
color: rgba(0, 0, 0, 0.87); |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
& ::ng-deep { |
|||
.mat-horizontal-stepper-header{ |
|||
pointer-events: none !important; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
///
|
|||
/// 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 { Type } from '@angular/core'; |
|||
import { TwoFactorAuthProviderType } from '@shared/models/two-factor-auth.models'; |
|||
import { TotpAuthDialogComponent } from './totp-auth-dialog.component'; |
|||
import { SMSAuthDialogComponent } from './sms-auth-dialog.component'; |
|||
import { EmailAuthDialogComponent } from './email-auth-dialog.component'; |
|||
|
|||
export const authenticationDialogMap = new Map<TwoFactorAuthProviderType, Type<any>>( |
|||
[ |
|||
[TwoFactorAuthProviderType.TOTP, TotpAuthDialogComponent], |
|||
[TwoFactorAuthProviderType.SMS, SMSAuthDialogComponent], |
|||
[TwoFactorAuthProviderType.EMAIL, EmailAuthDialogComponent] |
|||
] |
|||
); |
|||
@ -0,0 +1,102 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2 translate>security.2fa.dialog.enable-email-title</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="closeDialog()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content tb-toast style="padding: 0"> |
|||
<mat-stepper [linear]="true" #stepper> |
|||
<ng-template matStepperIcon="edit"> |
|||
<mat-icon>done</mat-icon> |
|||
</ng-template> |
|||
<mat-step [stepControl]="emailConfigForm"> |
|||
<ng-template matStepLabel>{{ 'security.2fa.dialog.email-step-label' | translate }}</ng-template> |
|||
<form [formGroup]="emailConfigForm" (ngSubmit)="nextStep()"> |
|||
<p class="mat-body step-description input" translate>security.2fa.dialog.email-step-description</p> |
|||
<div fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input matInput formControlName="email" |
|||
placeholder="{{ 'security.2fa.dialog.email-step-label' | translate }}" /> |
|||
<mat-error *ngIf="emailConfigForm.get('email').hasError('required')"> |
|||
{{ 'user.email-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="emailConfigForm.get('email').hasError('email')"> |
|||
{{ 'user.invalid-email-format' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<button mat-raised-button |
|||
type="submit" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || emailConfigForm.invalid"> |
|||
{{ 'security.2fa.dialog.send-code' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step [stepControl]="emailVerificationForm"> |
|||
<ng-template matStepLabel>{{ 'security.2fa.dialog.verification-step-label' | translate }}</ng-template> |
|||
<form [formGroup]="emailVerificationForm" (ngSubmit)="nextStep()"> |
|||
<p class="mat-body step-description input"> |
|||
{{ 'security.2fa.dialog.verification-step-description' | translate : {address: emailConfigForm.get('email').value} }} |
|||
</p> |
|||
<div fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input matInput formControlName="verificationCode" |
|||
maxlength="6" type="text" |
|||
pattern="\d*" |
|||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}"> |
|||
<mat-error *ngIf="emailVerificationForm.get('verificationCode').invalid"> |
|||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<button mat-raised-button |
|||
type="submit" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || emailVerificationForm.invalid"> |
|||
{{ 'security.2fa.dialog.activate' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step> |
|||
<ng-template matStepLabel matStepperIcon="done">{{ 'security.2fa.dialog.activation-step-label' | translate }}</ng-template> |
|||
<div> |
|||
<h3 class="result-title" translate>security.2fa.dialog.success</h3> |
|||
<p class="mat-body result-description" translate>security.2fa.dialog.activation-step-description-email</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[mat-dialog-close]="true"> |
|||
{{ 'import.stepper-text.done' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
</mat-stepper> |
|||
</div> |
|||
@ -0,0 +1,105 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2 translate>security.2fa.dialog.enable-sms-title</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="closeDialog()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content tb-toast style="padding: 0"> |
|||
<mat-stepper linear #stepper> |
|||
<ng-template matStepperIcon="edit"> |
|||
<mat-icon>done</mat-icon> |
|||
</ng-template> |
|||
<mat-step [stepControl]="smsConfigForm"> |
|||
<ng-template matStepLabel>{{ 'security.2fa.dialog.sms-step-label' | translate }}</ng-template> |
|||
<form [formGroup]="smsConfigForm" (ngSubmit)="nextStep()"> |
|||
<p class="mat-body step-description input" translate>security.2fa.dialog.sms-step-description</p> |
|||
<div fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input type="tel" |
|||
[pattern]="phoneNumberPattern" |
|||
matInput formControlName="phone" |
|||
placeholder="{{ 'security.2fa.dialog.sms-step-label' | translate }}"> |
|||
<mat-error *ngIf="smsConfigForm.get('phone').hasError('required')"> |
|||
{{ 'admin.number-to-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="smsConfigForm.get('phone').hasError('pattern')"> |
|||
{{ 'admin.phone-number-pattern' | translate }} |
|||
</mat-error> |
|||
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint> |
|||
</mat-form-field> |
|||
<button mat-raised-button |
|||
type="submit" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || smsConfigForm.invalid"> |
|||
{{ 'security.2fa.dialog.send-code' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step [stepControl]="smsVerificationForm"> |
|||
<ng-template matStepLabel>{{ 'security.2fa.dialog.verification-step-label' | translate }}</ng-template> |
|||
<form [formGroup]="smsVerificationForm" (ngSubmit)="nextStep()"> |
|||
<p class="mat-body step-description input"> |
|||
{{ 'security.2fa.dialog.verification-step-description' | translate : {address: smsConfigForm.get('phone').value} }} |
|||
</p> |
|||
<div fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input matInput formControlName="verificationCode" |
|||
maxlength="6" type="text" |
|||
pattern="\d*" |
|||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}"> |
|||
<mat-error *ngIf="smsVerificationForm.get('verificationCode').invalid"> |
|||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<button mat-raised-button |
|||
type="submit" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || smsVerificationForm.invalid"> |
|||
{{ 'security.2fa.dialog.activate' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</mat-step> |
|||
<mat-step> |
|||
<ng-template matStepLabel matStepperIcon="done">{{ 'security.2fa.dialog.activation-step-label' | translate }}</ng-template> |
|||
<div> |
|||
<h3 class="result-title" translate>security.2fa.dialog.success</h3> |
|||
<p class="mat-body result-description" translate>security.2fa.dialog.activation-step-description-sms</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[mat-dialog-close]="true"> |
|||
{{ 'import.stepper-text.done' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
</mat-stepper> |
|||
</div> |
|||
@ -0,0 +1,92 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2 translate>security.2fa.dialog.enable-totp-title</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="closeDialog()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content tb-toast style="padding: 0"> |
|||
<mat-stepper linear #stepper> |
|||
<ng-template matStepperIcon="edit"> |
|||
<mat-icon>done</mat-icon> |
|||
</ng-template> |
|||
<mat-step> |
|||
<ng-template matStepLabel>{{ 'security.2fa.dialog.totp-step-label' | translate }}</ng-template> |
|||
<div> |
|||
<p class="mat-body-1" translate>security.2fa.dialog.totp-step-description-open</p> |
|||
<p class="mat-body-1" translate>security.2fa.dialog.totp-step-description-install</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
matStepperNext> |
|||
{{ 'security.2fa.dialog.next' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
<mat-step [stepControl]="totpConfigForm"> |
|||
<ng-template matStepLabel>{{ 'security.2fa.dialog.verification-step-label' | translate }}</ng-template> |
|||
<form [formGroup]="totpConfigForm" fxLayoutAlign="start center" fxLayout="column" (ngSubmit)="onSaveConfig()"> |
|||
<p class="mat-body qr-code-description" translate>security.2fa.dialog.scan-qr-code</p> |
|||
<canvas fxFlex #canvas [ngStyle]="{display: totpAuthURL ? 'block' : 'none'}"></canvas> |
|||
<p class="mat-body qr-code-description" style="margin-top: 30px;" translate>security.2fa.dialog.enter-verification-code</p> |
|||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input matInput formControlName="verificationCode" |
|||
maxlength="6" type="text" |
|||
pattern="\d*" |
|||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}"> |
|||
<mat-error *ngIf="totpConfigForm.get('verificationCode').invalid"> |
|||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</form> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-raised-button |
|||
type="submit" |
|||
color="primary" |
|||
[disabled]="(isLoading$ | async) || totpConfigForm.invalid"> |
|||
{{ 'security.2fa.dialog.next' | translate }} |
|||
</button> |
|||
</div> |
|||
</mat-step> |
|||
<mat-step> |
|||
<ng-template matStepLabel matStepperIcon="done">{{ 'security.2fa.dialog.activation-step-label' | translate }}</ng-template> |
|||
<div> |
|||
<h3 class="result-title" translate>security.2fa.dialog.success</h3> |
|||
<p class="mat-body result-description" translate>security.2fa.dialog.activation-step-description-totp</p> |
|||
<div fxLayout="row" fxLayoutAlign="end center"> |
|||
<button mat-raised-button |
|||
type="button" |
|||
color="primary" |
|||
[mat-dialog-close]="true"> |
|||
{{ 'import.stepper-text.done' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</mat-step> |
|||
</mat-stepper> |
|||
</div> |
|||
@ -0,0 +1,84 @@ |
|||
///
|
|||
/// 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, NgModule } from '@angular/core'; |
|||
import { Resolve, RouterModule, Routes } from '@angular/router'; |
|||
|
|||
import { SecurityComponent } from './security.component'; |
|||
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { User } from '@shared/models/user.model'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { UserService } from '@core/http/user.service'; |
|||
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
|||
import { Observable } from 'rxjs'; |
|||
import { TwoFactorAuthProviderType } from '@shared/models/two-factor-auth.models'; |
|||
import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service'; |
|||
|
|||
@Injectable() |
|||
export class UserProfileResolver implements Resolve<User> { |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private userService: UserService) { |
|||
} |
|||
|
|||
resolve(): Observable<User> { |
|||
const userId = getCurrentAuthUser(this.store).userId; |
|||
return this.userService.getUser(userId); |
|||
} |
|||
} |
|||
|
|||
@Injectable() |
|||
export class UserTwoFAProvidersResolver implements Resolve<Array<TwoFactorAuthProviderType>> { |
|||
|
|||
constructor(private twoFactorAuthService: TwoFactorAuthenticationService) { |
|||
} |
|||
|
|||
resolve(): Observable<Array<TwoFactorAuthProviderType>> { |
|||
return this.twoFactorAuthService.getAvailableTwoFaProviders(); |
|||
} |
|||
} |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'security', |
|||
component: SecurityComponent, |
|||
canDeactivate: [ConfirmOnExitGuard], |
|||
data: { |
|||
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], |
|||
title: 'security.security', |
|||
breadcrumb: { |
|||
label: 'security.security', |
|||
icon: 'lock' |
|||
} |
|||
}, |
|||
resolve: { |
|||
user: UserProfileResolver, |
|||
providers: UserTwoFAProvidersResolver |
|||
} |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule], |
|||
providers: [ |
|||
UserProfileResolver, |
|||
UserTwoFAProvidersResolver |
|||
] |
|||
}) |
|||
export class SecurityRoutingModule { } |
|||
@ -0,0 +1,80 @@ |
|||
<!-- |
|||
|
|||
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 class="profile-container" fxLayout="column" fxLayoutGap="8px"> |
|||
<mat-card class="profile-card" fxLayout="column"> |
|||
<mat-card-title> |
|||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.xs="8px" |
|||
fxLayoutAlign="space-between start" fxLayoutAlign.xs="start start"> |
|||
<div fxFlex class="mat-headline" translate> |
|||
security.security |
|||
</div> |
|||
<div fxLayout="column"> |
|||
<span class="mat-subheader" translate>profile.last-login-time</span> |
|||
<span class="profile-last-login-ts" style='opacity: 0.7;'>{{ user?.additionalInfo?.lastLoginTs | date:'yyyy-MM-dd HH:mm:ss' }}</span> |
|||
</div> |
|||
</div> |
|||
</mat-card-title> |
|||
<mat-card-content> |
|||
<div> |
|||
<button mat-stroked-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="copyToken()"> |
|||
<mat-icon class="material-icons">add_circle_outline</mat-icon> |
|||
<span>{{ 'profile.copy-jwt-token' | translate }}</span> |
|||
</button> |
|||
<div class="profile-btn-subtext">{{ expirationJwtData }}</div> |
|||
</div> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
<mat-card class="profile-card" *ngIf="allowTwoFactorProviders.length"> |
|||
<mat-card-title style="margin-bottom: 20px;"> |
|||
<span class="mat-headline" translate>admin.2fa.2fa</span> |
|||
</mat-card-title> |
|||
<mat-card-subtitle style="margin-bottom: 40px;"> |
|||
<div class="mat-body-1 description" translate>security.2fa.2fa-description</div> |
|||
</mat-card-subtitle> |
|||
<mat-card-content> |
|||
<h3 class="mat-h3 auth-title" translate>security.2fa.authenticate-with</h3> |
|||
<form [formGroup]="twoFactorAuth"> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<mat-radio-group formControlName="useByDefault"> |
|||
<ng-container *ngFor="let provider of allowTwoFactorProviders; let $last = last; trackBy: trackByProvider"> |
|||
<div class="provider"> |
|||
<h4 class="provider-title">{{ providersData.get(provider).name | translate }}</h4> |
|||
<div fxLayout="row" fxLayoutAlign="space-between start"> |
|||
<div class="mat-body-1 description"> |
|||
{{ providersData.get(provider).description | translate }} |
|||
</div> |
|||
<mat-slide-toggle [formControlName]="provider" |
|||
(click)="confirm2FAChange($event, provider)"> |
|||
</mat-slide-toggle> |
|||
</div> |
|||
<mat-radio-button [value]="provider" |
|||
*ngIf="twoFactorAuth.get(provider).value"> |
|||
<span class="checkbox-label" translate>security.2fa.main-2fa-method</span> |
|||
</mat-radio-button> |
|||
</div> |
|||
<mat-divider *ngIf="!$last"></mat-divider> |
|||
</ng-container> |
|||
</mat-radio-group> |
|||
</fieldset> |
|||
</form> |
|||
</mat-card-content> |
|||
</mat-card> |
|||
</div> |
|||
@ -0,0 +1,115 @@ |
|||
/** |
|||
* 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 "../../../../../scss/constants"; |
|||
|
|||
:host { |
|||
.profile-container { |
|||
padding: 8px; |
|||
} |
|||
mat-card.profile-card { |
|||
@media #{$mat-gt-sm} { |
|||
width: 70%; |
|||
} |
|||
@media #{$mat-gt-md} { |
|||
width: 50%; |
|||
} |
|||
@media #{$mat-gt-xl} { |
|||
width: 45%; |
|||
} |
|||
.title-container { |
|||
margin: 0; |
|||
} |
|||
.profile-email { |
|||
font-size: 16px; |
|||
font-weight: 400; |
|||
} |
|||
.mat-subheader { |
|||
line-height: 24px; |
|||
color: rgba(0,0,0,0.54); |
|||
font-size: 14px; |
|||
font-weight: 400; |
|||
} |
|||
.profile-last-login-ts { |
|||
font-size: 16px; |
|||
font-weight: 400; |
|||
} |
|||
.profile-btn-subtext { |
|||
font: 400 14px / 16px Roboto, "Helvetica Neue", sans-serif; |
|||
letter-spacing: 0.25px; |
|||
opacity: 0.6; |
|||
padding: 8px 0; |
|||
} |
|||
.tb-home-dashboard { |
|||
tb-dashboard-autocomplete { |
|||
@media #{$mat-gt-sm} { |
|||
padding-right: 12px; |
|||
} |
|||
|
|||
@media #{$mat-lt-md} { |
|||
padding-bottom: 12px; |
|||
} |
|||
} |
|||
mat-checkbox { |
|||
@media #{$mat-gt-sm} { |
|||
margin-top: 16px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.description { |
|||
color: rgba(0, 0, 0, 0.54); |
|||
letter-spacing: 0.25px; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.auth-title { |
|||
font-weight: 500; |
|||
line-height: 20px; |
|||
letter-spacing: 0.25px; |
|||
margin: 0; |
|||
} |
|||
|
|||
.mat-divider-horizontal { |
|||
left: 16px; |
|||
right: 16px; |
|||
width: auto; |
|||
} |
|||
|
|||
.provider { |
|||
padding: 24px 0; |
|||
|
|||
.provider-title { |
|||
font: 400 14px / 16px Roboto, "Helvetica Neue", sans-serif; |
|||
letter-spacing: 0.25px; |
|||
margin: 0 0 8px; |
|||
} |
|||
|
|||
.description { |
|||
max-width: 85%; |
|||
} |
|||
|
|||
.checkbox-label { |
|||
font-size: 14px; |
|||
letter-spacing: 0.25px; |
|||
opacity: 0.87; |
|||
} |
|||
|
|||
.mat-radio-button { |
|||
margin-top: 8px; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
///
|
|||
/// 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, OnInit } from '@angular/core'; |
|||
import { User } from '@shared/models/user.model'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { FormBuilder, FormGroup } from '@angular/forms'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { ActivatedRoute } from '@angular/router'; |
|||
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
|||
import { DatePipe } from '@angular/common'; |
|||
import { ClipboardService } from 'ngx-clipboard'; |
|||
import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service'; |
|||
import { |
|||
AccountTwoFaSettings, |
|||
twoFactorAuthProvidersData, |
|||
TwoFactorAuthProviderType |
|||
} from '@shared/models/two-factor-auth.models'; |
|||
import { authenticationDialogMap } from '@home/pages/security/authentication-dialog/authentication-dialog.map'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-security', |
|||
templateUrl: './security.component.html', |
|||
styleUrls: ['./security.component.scss'] |
|||
}) |
|||
export class SecurityComponent extends PageComponent implements OnInit { |
|||
|
|||
twoFactorAuth: FormGroup; |
|||
user: User; |
|||
allowTwoFactorProviders: TwoFactorAuthProviderType[] = []; |
|||
providersData = twoFactorAuthProvidersData; |
|||
|
|||
get jwtToken(): string { |
|||
return `Bearer ${localStorage.getItem('jwt_token')}`; |
|||
} |
|||
|
|||
get jwtTokenExpiration(): string { |
|||
return localStorage.getItem('jwt_token_expiration'); |
|||
} |
|||
|
|||
get expirationJwtData(): string { |
|||
const expirationData = this.datePipe.transform(this.jwtTokenExpiration, 'yyyy-MM-dd HH:mm:ss'); |
|||
return this.translate.instant('profile.valid-till', { expirationData }); |
|||
} |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private route: ActivatedRoute, |
|||
private translate: TranslateService, |
|||
private twoFaService: TwoFactorAuthenticationService, |
|||
public dialog: MatDialog, |
|||
public dialogService: DialogService, |
|||
public fb: FormBuilder, |
|||
private datePipe: DatePipe, |
|||
private clipboardService: ClipboardService) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.buildTwoFactorForm(); |
|||
this.user = this.route.snapshot.data.user; |
|||
this.twoFactorLoad(this.route.snapshot.data.providers); |
|||
} |
|||
|
|||
private buildTwoFactorForm() { |
|||
this.twoFactorAuth = this.fb.group({ |
|||
TOTP: [false], |
|||
SMS: [false], |
|||
EMAIL: [false], |
|||
useByDefault: [null] |
|||
}); |
|||
this.twoFactorAuth.get('useByDefault').valueChanges.subscribe(value => { |
|||
this.twoFaService.updateTwoFaAccountConfig(value, true, {ignoreLoading: true}) |
|||
.subscribe(data => this.processTwoFactorAuthConfig(data)); |
|||
}); |
|||
} |
|||
|
|||
private twoFactorLoad(providers: TwoFactorAuthProviderType[]) { |
|||
if (providers.length) { |
|||
this.twoFaService.getAccountTwoFaSettings().subscribe(data => this.processTwoFactorAuthConfig(data)); |
|||
Object.values(TwoFactorAuthProviderType).forEach(type => { |
|||
if (providers.includes(type)) { |
|||
this.allowTwoFactorProviders.push(type); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private processTwoFactorAuthConfig(setting: AccountTwoFaSettings) { |
|||
const configs = setting.configs; |
|||
Object.values(TwoFactorAuthProviderType).forEach(provider => { |
|||
if (configs[provider]) { |
|||
this.twoFactorAuth.get(provider).setValue(true); |
|||
if (configs[provider].useByDefault) { |
|||
this.twoFactorAuth.get('useByDefault').setValue(provider, {emitEvent: false}); |
|||
} |
|||
} else { |
|||
this.twoFactorAuth.get(provider).setValue(false); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
trackByProvider(i: number, provider: TwoFactorAuthProviderType) { |
|||
return provider; |
|||
} |
|||
|
|||
copyToken() { |
|||
if (+this.jwtTokenExpiration < Date.now()) { |
|||
this.store.dispatch(new ActionNotificationShow({ |
|||
message: this.translate.instant('profile.tokenCopiedWarnMessage'), |
|||
type: 'warn', |
|||
duration: 1500, |
|||
verticalPosition: 'bottom', |
|||
horizontalPosition: 'right' |
|||
})); |
|||
} else { |
|||
this.clipboardService.copyFromContent(this.jwtToken); |
|||
this.store.dispatch(new ActionNotificationShow({ |
|||
message: this.translate.instant('profile.tokenCopiedSuccessMessage'), |
|||
type: 'success', |
|||
duration: 750, |
|||
verticalPosition: 'bottom', |
|||
horizontalPosition: 'right' |
|||
})); |
|||
} |
|||
} |
|||
|
|||
confirm2FAChange(event: MouseEvent, provider: TwoFactorAuthProviderType) { |
|||
event.stopPropagation(); |
|||
event.preventDefault(); |
|||
if (this.twoFactorAuth.get(provider).value) { |
|||
const providerName = this.translate.instant(`security.2fa.provider.${provider.toLowerCase()}`); |
|||
this.dialogService.confirm( |
|||
this.translate.instant('security.2fa.disable-2fa-provider-title', {name: providerName}), |
|||
this.translate.instant('security.2fa.disable-2fa-provider-text', {name: providerName}), |
|||
).subscribe(res => { |
|||
if (res) { |
|||
this.twoFaService.deleteTwoFaAccountConfig(provider).subscribe(data => this.processTwoFactorAuthConfig(data)); |
|||
} |
|||
}); |
|||
} else { |
|||
const dialogData = provider === TwoFactorAuthProviderType.EMAIL ? {email: this.user.email} : {}; |
|||
this.dialog.open(authenticationDialogMap.get(provider), { |
|||
disableClose: true, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|||
data: dialogData |
|||
}).afterClosed().subscribe(res => { |
|||
if (res) { |
|||
this.twoFactorAuth.get(provider).setValue(res); |
|||
this.twoFactorAuth.get('useByDefault').setValue(provider, {emitEvent: false}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
///
|
|||
/// 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 { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SecurityComponent } from './security.component'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { SecurityRoutingModule } from './security-routing.module'; |
|||
import { TotpAuthDialogComponent } from './authentication-dialog/totp-auth-dialog.component'; |
|||
import { SMSAuthDialogComponent } from '@home/pages/security/authentication-dialog/sms-auth-dialog.component'; |
|||
import { EmailAuthDialogComponent } from '@home/pages/security/authentication-dialog/email-auth-dialog.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
SecurityComponent, |
|||
TotpAuthDialogComponent, |
|||
SMSAuthDialogComponent, |
|||
EmailAuthDialogComponent |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
SecurityRoutingModule |
|||
] |
|||
}) |
|||
export class SecurityModule { } |
|||
Loading…
Reference in new issue