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:
parent
a1442194ae
commit
6fadee7cb4
|
@ -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">×</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>
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue