PM-2055 Update Two Factor Authenticator Dialog (#8972)

* PM-2055 Update Two Factor Authenticator Dialog

* PM-2055 Added close to disable two factor

* PM-2055 Added a event emitter to capture enabled status
This commit is contained in:
KiruthigaManivannan 2024-06-06 19:23:29 +05:30 committed by GitHub
parent a1442194ae
commit 6fadee7cb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 119 additions and 125 deletions

View File

@ -1,109 +1,80 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="2faAuthenticatorTitle"> <form *ngIf="authed" [formGroup]="formGroup" [bitSubmit]="submit">
<div class="modal-dialog" role="document"> <bit-dialog dialogSize="default">
<div class="modal-content"> <span bitDialogTitle
<div class="modal-header"> >{{ "twoStepLogin" | i18n }}
<h1 class="modal-title" id="2faAuthenticatorTitle"> <span bitTypography="body1">{{ "authenticatorAppTitle" | i18n }}</span>
{{ "twoStepLogin" | i18n }} </span>
<small>{{ "authenticatorAppTitle" | i18n }}</small> <ng-container bitDialogContent>
</h1> <ng-container *ngIf="!enabled">
<button <img class="float-right mfaType0" alt="Authenticator app logo" />
type="button" <p bitTypography="body1">{{ "twoStepAuthenticatorDesc" | i18n }}</p>
class="close" <p bitTypography="body1" class="tw-font-bold">
data-dismiss="modal" 1. {{ "twoStepAuthenticatorDownloadApp" | i18n }}
appA11yTitle="{{ 'close' | i18n }}" </p>
> </ng-container>
<span aria-hidden="true">&times;</span> <ng-container *ngIf="enabled">
</button> <app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
</div> <p bitTypography="body1">{{ "twoStepLoginProviderEnabled" | i18n }}</p>
<form {{ "twoStepAuthenticatorReaddDesc" | i18n }}
#form </app-callout>
(ngSubmit)="submit()" <img class="float-right mfaType0" alt="Authenticator app logo" />
[appApiAction]="formPromise" <p bitTypography="body1">{{ "twoStepAuthenticatorNeedApp" | i18n }}</p>
ngNativeValidate </ng-container>
*ngIf="authed" <ul class="bwi-ul">
> <li>
<div class="modal-body"> <i class="bwi bwi-li bwi-apple"></i>{{ "iosDevices" | i18n }}:
<ng-container *ngIf="!enabled"> <a
<img class="float-right mfaType0" alt="Authenticator app logo" /> bitLink
<p>{{ "twoStepAuthenticatorDesc" | i18n }}</p> href="https://itunes.apple.com/us/app/authy/id494168017?mt=8"
<p> target="_blank"
<strong>1. {{ "twoStepAuthenticatorDownloadApp" | i18n }}</strong> rel="noreferrer"
</p> >Authy</a
</ng-container> >
<ng-container *ngIf="enabled"> </li>
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle"> <li>
<p>{{ "twoStepLoginProviderEnabled" | i18n }}</p> <i class="bwi bwi-li bwi-android"></i>{{ "androidDevices" | i18n }}:
{{ "twoStepAuthenticatorReaddDesc" | i18n }} <a
</app-callout> bitLink
<img class="float-right mfaType0" alt="Authenticator app logo" /> href="https://play.google.com/store/apps/details?id=com.authy.authy"
<p>{{ "twoStepAuthenticatorNeedApp" | i18n }}</p> target="_blank"
</ng-container> rel="noreferrer"
<ul class="bwi-ul"> >Authy</a
<li> >
<i class="bwi bwi-li bwi-apple"></i>{{ "iosDevices" | i18n }}: </li>
<a <li>
href="https://itunes.apple.com/us/app/authy/id494168017?mt=8" <i class="bwi bwi-li bwi-windows"></i>{{ "windowsDevices" | i18n }}:
target="_blank" <a
rel="noreferrer" bitLink
>Authy</a href="https://www.microsoft.com/p/authenticator/9wzdncrfj3rj"
> target="_blank"
</li> rel="noreferrer"
<li> >Microsoft Authenticator</a
<i class="bwi bwi-li bwi-android"></i>{{ "androidDevices" | i18n }}: >
<a </li>
href="https://play.google.com/store/apps/details?id=com.authy.authy" </ul>
target="_blank" <p bitTypography="body1">{{ "twoStepAuthenticatorAppsRecommended" | i18n }}</p>
rel="noreferrer" <p *ngIf="!enabled" bitTypography="body1" class="tw-font-bold">
>Authy</a 2. {{ "twoStepAuthenticatorScanCode" | i18n }}
> </p>
</li> <hr *ngIf="enabled" />
<li> <p class="text-center" [ngClass]="{ 'mb-0': enabled }">
<i class="bwi bwi-li bwi-windows"></i>{{ "windowsDevices" | i18n }}: <canvas id="qr"></canvas><br />
<a <code appA11yTitle="{{ 'key' | i18n }}">{{ key }}</code>
href="https://www.microsoft.com/p/authenticator/9wzdncrfj3rj" </p>
target="_blank" <ng-container *ngIf="!enabled">
rel="noreferrer" <bit-form-field>
>Microsoft Authenticator</a <bit-label>3. {{ "twoStepAuthenticatorEnterCode" | i18n }}</bit-label>
> <input bitInput type="text" formControlName="token" appInputVerbatim />
</li> </bit-form-field>
</ul> </ng-container>
<p>{{ "twoStepAuthenticatorAppsRecommended" | i18n }}</p> </ng-container>
<p *ngIf="!enabled"> <ng-container bitDialogFooter>
<strong>2. {{ "twoStepAuthenticatorScanCode" | i18n }}</strong> <button bitButton bitFormButton type="submit" buttonType="primary">
</p> {{ (enabled ? "disable" : "enable") | i18n }}
<hr *ngIf="enabled" /> </button>
<p class="text-center" [ngClass]="{ 'mb-0': enabled }"> <button bitButton bitFormButton type="button" buttonType="secondary" [bitAction]="close">
<canvas id="qr"></canvas><br /> {{ "close" | i18n }}
<code appA11yTitle="{{ 'key' | i18n }}">{{ key }}</code> </button>
</p> </ng-container>
<ng-container *ngIf="!enabled"> </bit-dialog>
<label for="token">3. {{ "twoStepAuthenticatorEnterCode" | i18n }}</label> </form>
<input
id="token"
type="text"
name="Token"
class="form-control"
[(ngModel)]="token"
required
appInputVerbatim
/>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span *ngIf="!enabled">{{ "enable" | i18n }}</span>
<span *ngIf="enabled">{{ "disable" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,4 +1,6 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@ -38,15 +40,21 @@ export class TwoFactorAuthenticatorComponent
extends TwoFactorBaseComponent extends TwoFactorBaseComponent
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
@Output() onChangeStatus = new EventEmitter<boolean>();
type = TwoFactorProviderType.Authenticator; type = TwoFactorProviderType.Authenticator;
key: string; key: string;
token: string;
formPromise: Promise<TwoFactorAuthenticatorResponse>; formPromise: Promise<TwoFactorAuthenticatorResponse>;
override componentName = "app-two-factor-authenticator"; override componentName = "app-two-factor-authenticator";
private qrScript: HTMLScriptElement; private qrScript: HTMLScriptElement;
protected formGroup = new FormGroup({
token: new FormControl(null, [Validators.required]),
});
constructor( constructor(
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorAuthenticatorResponse>,
private dialogRef: DialogRef,
apiService: ApiService, apiService: ApiService,
i18nService: I18nService, i18nService: I18nService,
userVerificationService: UserVerificationService, userVerificationService: UserVerificationService,
@ -68,8 +76,9 @@ export class TwoFactorAuthenticatorComponent
this.qrScript.async = true; this.qrScript.async = true;
} }
ngOnInit() { async ngOnInit() {
window.document.body.appendChild(this.qrScript); window.document.body.appendChild(this.qrScript);
await this.auth(this.data);
} }
ngOnDestroy() { ngOnDestroy() {
@ -81,17 +90,24 @@ export class TwoFactorAuthenticatorComponent
return this.processResponse(authResponse.response); return this.processResponse(authResponse.response);
} }
submit() { submit = async () => {
if (this.enabled) { if (this.enabled) {
return super.disable(this.formPromise); await this.disableAuthentication(this.formPromise);
this.onChangeStatus.emit(this.enabled);
this.close();
} else { } else {
return this.enable(); await this.enable();
this.onChangeStatus.emit(this.enabled);
} }
};
private async disableAuthentication(promise: Promise<unknown>) {
return super.disable(promise);
} }
protected async enable() { protected async enable() {
const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest); const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest);
request.token = this.token; request.token = this.formGroup.value.token;
request.key = this.key; request.key = this.key;
return super.enable(async () => { return super.enable(async () => {
@ -102,7 +118,7 @@ export class TwoFactorAuthenticatorComponent
} }
private async processResponse(response: TwoFactorAuthenticatorResponse) { private async processResponse(response: TwoFactorAuthenticatorResponse) {
this.token = null; this.formGroup.get("token").setValue(null);
this.enabled = response.enabled; this.enabled = response.enabled;
this.key = response.key; this.key = response.key;
const email = await firstValueFrom( const email = await firstValueFrom(
@ -121,4 +137,15 @@ export class TwoFactorAuthenticatorComponent
}); });
}, 100); }, 100);
} }
close = () => {
this.dialogRef.close(this.enabled);
};
static open(
dialogService: DialogService,
config: DialogConfig<AuthResponse<TwoFactorAuthenticatorResponse>>,
) {
return dialogService.open<boolean>(TwoFactorAuthenticatorComponent, config);
}
} }

View File

@ -80,7 +80,6 @@
</ul> </ul>
</bit-container> </bit-container>
<ng-template #authenticatorTemplate></ng-template>
<ng-template #duoTemplate></ng-template> <ng-template #duoTemplate></ng-template>
<ng-template #emailTemplate></ng-template> <ng-template #emailTemplate></ng-template>
<ng-template #yubikeyTemplate></ng-template> <ng-template #yubikeyTemplate></ng-template>

View File

@ -34,8 +34,6 @@ import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component";
templateUrl: "two-factor-setup.component.html", templateUrl: "two-factor-setup.component.html",
}) })
export class TwoFactorSetupComponent implements OnInit, OnDestroy { export class TwoFactorSetupComponent implements OnInit, OnDestroy {
@ViewChild("authenticatorTemplate", { read: ViewContainerRef, static: true })
authenticatorModalRef: ViewContainerRef;
@ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true }) @ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true })
yubikeyModalRef: ViewContainerRef; yubikeyModalRef: ViewContainerRef;
@ViewChild("duoTemplate", { read: ViewContainerRef, static: true }) duoModalRef: ViewContainerRef; @ViewChild("duoTemplate", { read: ViewContainerRef, static: true }) duoModalRef: ViewContainerRef;
@ -137,12 +135,11 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
if (!result) { if (!result) {
return; return;
} }
const authComp = await this.openModal( const authComp: DialogRef<boolean, any> = TwoFactorAuthenticatorComponent.open(
this.authenticatorModalRef, this.dialogService,
TwoFactorAuthenticatorComponent, { data: result },
); );
await authComp.auth(result); authComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => {
authComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Authenticator); this.updateStatus(enabled, TwoFactorProviderType.Authenticator);
}); });
break; break;