UI: Add secret for totp auth dialog
This commit is contained in:
parent
b84d818b28
commit
4ddc8030cc
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { Component, DestroyRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
||||
import { Store } from '@ngrx/store';
|
||||
@ -29,11 +29,10 @@ import {
|
||||
TwoFactorAuthSettingsForm
|
||||
} from '@shared/models/two-factor-auth.models';
|
||||
import { isDefined, isNotEmptyStr } from '@core/utils';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { MatExpansionPanel } from '@angular/material/expansion';
|
||||
import { NotificationTargetConfigType, NotificationTargetConfigTypeInfoMap } from '@shared/models/notification.models';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-2fa-settings',
|
||||
@ -42,7 +41,6 @@ import { EntityType } from '@shared/models/entity-type.models';
|
||||
})
|
||||
export class TwoFactorAuthSettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy {
|
||||
|
||||
private readonly destroy$ = new Subject<void>();
|
||||
private readonly posIntValidation = [Validators.required, Validators.min(1), Validators.pattern(/^\d*$/)];
|
||||
|
||||
twoFaFormGroup: UntypedFormGroup;
|
||||
@ -62,7 +60,8 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private twoFaService: TwoFactorAuthenticationService,
|
||||
private fb: UntypedFormBuilder) {
|
||||
private fb: UntypedFormBuilder,
|
||||
private destroyRef: DestroyRef) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
@ -75,8 +74,6 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
confirmForm(): UntypedFormGroup {
|
||||
@ -156,7 +153,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
|
||||
this.buildProvidersSettingsForm(provider);
|
||||
});
|
||||
this.twoFaFormGroup.get('verificationCodeCheckRateLimitEnable').valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
if (value) {
|
||||
this.twoFaFormGroup.get('verificationCodeCheckRateLimitNumber').enable({emitEvent: false});
|
||||
@ -167,7 +164,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
|
||||
}
|
||||
});
|
||||
this.providersForm.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe((value: TwoFactorAuthProviderConfigForm[]) => {
|
||||
const activeProvider = value.filter(provider => provider.enable);
|
||||
const indexBackupCode = Object.values(TwoFactorAuthProviderType).indexOf(TwoFactorAuthProviderType.BACKUP_CODE);
|
||||
@ -181,7 +178,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
|
||||
}
|
||||
});
|
||||
this.twoFaFormGroup.get('enforceTwoFa').valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
if (value) {
|
||||
this.twoFaFormGroup.get('enforcedUsersFilter').enable({emitEvent: false});
|
||||
@ -245,7 +242,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI
|
||||
}
|
||||
const newProviders = this.fb.group(formControlConfig);
|
||||
newProviders.get('enable').valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
if (value) {
|
||||
newProviders.enable({emitEvent: false});
|
||||
|
||||
@ -52,6 +52,19 @@
|
||||
<form [formGroup]="totpConfigForm" class="flex flex-col items-center justify-start" (ngSubmit)="onSaveConfig()">
|
||||
<p class="mat-body qr-code-description" translate>security.2fa.dialog.scan-qr-code</p>
|
||||
<canvas class="flex-1" #canvas [style.display]="totpAuthURL ? 'block' : 'none'"></canvas>
|
||||
<p class="mat-body qr-code-description" translate>login.enter-key-manually</p>
|
||||
<div class="flex flex-row items-center w-full overflow-hidden max-w-[375px]">
|
||||
<span tbTruncateWithTooltip class="w-full">{{ totpAuthURLSecret }}</span>
|
||||
<tb-copy-button
|
||||
class="attribute-copy"
|
||||
[disabled]="isLoading$ | async"
|
||||
[copyText]="totpAuthURLSecret"
|
||||
tooltipText="{{ 'attribute.copy-key' | translate }}"
|
||||
tooltipPosition="above"
|
||||
icon="content_copy"
|
||||
[style]="{'font-size': '24px'}">
|
||||
</tb-copy-button>
|
||||
</div>
|
||||
<p class="mat-body qr-code-description" style="margin-top: 30px;" translate>security.2fa.dialog.enter-verification-code</p>
|
||||
<mat-form-field class="mat-block code-container flex-1">
|
||||
<input matInput formControlName="verificationCode"
|
||||
|
||||
@ -42,6 +42,7 @@ export class TotpAuthDialogComponent extends DialogComponent<TotpAuthDialogCompo
|
||||
|
||||
totpConfigForm: UntypedFormGroup;
|
||||
totpAuthURL: string;
|
||||
totpAuthURLSecret: string;
|
||||
|
||||
@ViewChild('stepper', {static: false}) stepper: MatStepper;
|
||||
@ViewChild('canvas', {static: false}) canvasRef: ElementRef<HTMLCanvasElement>;
|
||||
@ -55,6 +56,7 @@ export class TotpAuthDialogComponent extends DialogComponent<TotpAuthDialogCompo
|
||||
this.twoFaService.generateTwoFaAccountConfig(TwoFactorAuthProviderType.TOTP).subscribe(accountConfig => {
|
||||
this.authAccountConfig = accountConfig as TotpTwoFactorAuthAccountConfig;
|
||||
this.totpAuthURL = this.authAccountConfig.authUrl;
|
||||
this.totpAuthURLSecret = new URL(this.totpAuthURL).searchParams.get('secret');
|
||||
this.authAccountConfig.useByDefault = true;
|
||||
import('qrcode').then((QRCode) => {
|
||||
unwrapModule(QRCode).toCanvas(this.canvasRef.nativeElement, this.totpAuthURL);
|
||||
|
||||
@ -31,12 +31,12 @@
|
||||
<mat-card-content>
|
||||
<div class="providers-container tb-default flex flex-col gap-2">
|
||||
<p class="mat-body"> {{ (config ? 'login.set-up-verification-method-login' :'login.set-up-verification-method') | translate }}</p>
|
||||
<ng-container *ngFor="let provider of allowProviders">
|
||||
@for (provider of allowProviders; track provider) {
|
||||
<button type="button" [disabled]="config?.configs?.[provider]" mat-stroked-button class="provider" (click)="updateState(provider)">
|
||||
<mat-icon class="tb-mat-18" svgIcon="{{ providersData.get(provider).icon }}"></mat-icon>
|
||||
{{ providersData.get(provider).name | translate }}
|
||||
</button>
|
||||
</ng-container>
|
||||
}
|
||||
@if (config) {
|
||||
<button type="button" mat-raised-button color="accent" class="navigation w-full" (click)="cancelLogin()">
|
||||
{{ 'login.login' | translate }}
|
||||
@ -63,7 +63,7 @@
|
||||
<p class="mat-body qr-code-description mb-4" translate>security.2fa.dialog.scan-qr-code</p>
|
||||
<canvas class="flex-1" #canvas [style.display]="totpAuthURL ? 'block' : 'none'"></canvas>
|
||||
<p class="mat-body qr-code-description" translate>login.enter-key-manually</p>
|
||||
<div class="flex flex-row mb-8 w-full overflow-hidden" style="align-items: center">
|
||||
<div class="flex flex-row items-center mb-8 w-full overflow-hidden">
|
||||
<span tbTruncateWithTooltip class="w-full">{{ totpAuthURLSecret }}</span>
|
||||
<tb-copy-button
|
||||
class="attribute-copy"
|
||||
@ -203,9 +203,9 @@
|
||||
<div mat-dialog-content tb-toast class="backup-code">
|
||||
<p class="mat-body-2 description" translate>security.2fa.dialog.backup-code-description</p>
|
||||
<div class="container">
|
||||
<div *ngFor="let code of backupCode?.codes" class="code">
|
||||
{{ code }}
|
||||
</div>
|
||||
@for (code of backupCode?.codes; track code) {
|
||||
<div class="code">{{ code }}</div>
|
||||
}
|
||||
</div>
|
||||
<div class="action-buttons flex flex-row items-center justify-start gap-4">
|
||||
<button type="button" mat-flat-button class="provider w-full" (click)="downloadFile()">
|
||||
@ -242,10 +242,14 @@
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p class="mat-body">
|
||||
<p class="mat-body inline-block">
|
||||
{{ twoFactorAuthProvidersEnterCodeCardTranslate.get(providerType).description | translate }}
|
||||
<ng-container *ngIf="providerType === TwoFactorAuthProviderType.SMS">{{ smsConfigForm.get('phone').value }}</ng-container>
|
||||
<ng-container *ngIf="providerType === TwoFactorAuthProviderType.EMAIL">{{ emailConfigForm.get('email').value }}</ng-container>
|
||||
@if (providerType === TwoFactorAuthProviderType.SMS) {
|
||||
<span>{{ smsConfigForm.get('phone').value }}</span>
|
||||
}
|
||||
@if (providerType === TwoFactorAuthProviderType.EMAIL) {
|
||||
<span>{{ emailConfigForm.get('email').value }}</span>
|
||||
}
|
||||
</p>
|
||||
<form [formGroup]="configForm" class="flex flex-col items-center justify-start">
|
||||
<mat-form-field class="mat-block w-full">
|
||||
|
||||
@ -511,7 +511,7 @@
|
||||
"verification-limitations": "Verification limitations",
|
||||
"verification-message-template-pattern": "Verification message need to contains pattern: ${code}",
|
||||
"verification-message-template-required": "Verification message template is required.",
|
||||
"within-time": "Within time (sec)",
|
||||
"within-time": "Within time",
|
||||
"within-time-pattern": "Time must be a positive integer.",
|
||||
"within-time-required": "Time is required.",
|
||||
"force-2fa": "Force two-factor authentication",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user