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