[PM-2057] update two factor email dialog (#8974)

* migrating two factor email component

* two factor email component migration

* two factor email component migration

* two factor email component migration
This commit is contained in:
vinith-kovan 2024-06-05 22:29:51 +05:30 committed by GitHub
parent 419c107f87
commit 24fb3f71f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 118 additions and 121 deletions

View File

@ -1,101 +1,53 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="2faEmailTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title" id="2faEmailTitle">
{{ "twoStepLogin" | i18n }}
<small>{{ "emailTitle" | i18n }}</small>
</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
*ngIf="authed"
>
<div class="modal-body">
<ng-container *ngIf="enabled">
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
{{ "twoStepLoginProviderEnabled" | i18n }}
</app-callout>
<strong>{{ "email" | i18n }}:</strong> {{ email }}
</ng-container>
<ng-container *ngIf="!enabled">
<p class="d-flex">
<span class="mr-3">{{ "twoFactorEmailDesc" | i18n }}</span>
<img class="float-right ml-auto mfaType1" alt="Email logo" />
</p>
<div class="form-group">
<label for="email">1. {{ "twoFactorEmailEnterEmail" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
class="form-control"
[(ngModel)]="email"
required
inputmode="email"
appInputVerbatim="false"
/>
</div>
<div class="mb-3 d-flex">
<button
#sendBtn
type="button"
class="btn btn-outline-primary btn-sm btn-submit align-self-start"
(click)="sendEmail()"
[appApiAction]="emailPromise"
[disabled]="$any(sendBtn).loading"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "sendEmail" | i18n }}</span>
</button>
<span class="text-success ml-3" *ngIf="sentEmail">
{{ "verificationCodeEmailSent" | i18n: sentEmail }}
</span>
</div>
<div class="form-group">
<label for="token">2. {{ "twoFactorEmailEnterCode" | i18n }}</label>
<input
id="token"
type="text"
name="Token"
class="form-control"
[(ngModel)]="token"
required
appInputVerbatim
/>
</div>
</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 }}
<form [formGroup]="formGroup" [bitSubmit]="submit" *ngIf="authed">
<bit-dialog>
<span bitDialogTitle>
{{ "twoStepLogin" | i18n }}
<span bitTypography="body1">{{ "emailTitle" | i18n }}</span>
</span>
<ng-container bitDialogContent>
<ng-container *ngIf="enabled">
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
{{ "twoStepLoginProviderEnabled" | i18n }}
</app-callout>
<strong>{{ "email" | i18n }}:</strong> {{ email }}
</ng-container>
<ng-container *ngIf="!enabled">
<p class="tw-flex">
<span class="tw-mr-3">{{ "twoFactorEmailDesc" | i18n }}</span>
<img class="tw-float-right tw-ml-auto mfaType1" alt="Email logo" />
</p>
<bit-form-field>
<bit-label>1. {{ "twoFactorEmailEnterEmail" | i18n }}</bit-label>
<input
bitInput
type="text"
formControlName="email"
inputmode="email"
appInputVerbatim="false"
/>
</bit-form-field>
<div class="tw-mb-3 tw-flex">
<button bitButton type="button" buttonType="primary" [bitAction]="sendEmail">
{{ "sendEmail" | i18n }}
</button>
<span class="tw-text-success tw-ml-3" *ngIf="sentEmail">
{{ "verificationCodeEmailSent" | i18n: sentEmail }}
</span>
</div>
</form>
</div>
</div>
</div>
<bit-form-field>
<bit-label>2. {{ "twoFactorEmailEnterCode" | i18n }}</bit-label>
<input bitInput type="text" formControlName="token" appInputVerbatim />
</bit-form-field>
</ng-container>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton bitFormButton type="submit" buttonType="primary">
<span *ngIf="!enabled">{{ "enable" | i18n }}</span>
<span *ngIf="enabled">{{ "disable" | i18n }}</span>
</button>
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@ -1,4 +1,6 @@
import { Component } from "@angular/core";
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, EventEmitter, Inject, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@ -19,18 +21,22 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component";
@Component({
selector: "app-two-factor-email",
templateUrl: "two-factor-email.component.html",
outputs: ["onUpdated"],
})
export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
@Output() onChangeStatus: EventEmitter<boolean> = new EventEmitter();
type = TwoFactorProviderType.Email;
email: string;
token: string;
sentEmail: string;
formPromise: Promise<TwoFactorEmailResponse>;
emailPromise: Promise<unknown>;
override componentName = "app-two-factor-email";
formGroup = this.formBuilder.group({
token: [null],
email: ["", [Validators.email, Validators.required]],
});
constructor(
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorEmailResponse>,
apiService: ApiService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
@ -38,6 +44,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
userVerificationService: UserVerificationService,
private accountService: AccountService,
dialogService: DialogService,
private formBuilder: FormBuilder,
private dialogRef: DialogRef,
) {
super(
apiService,
@ -48,31 +56,49 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
dialogService,
);
}
get token() {
return this.formGroup.get("token").value;
}
set token(value: string) {
this.formGroup.get("token").setValue(value);
}
get email() {
return this.formGroup.get("email").value;
}
set email(value: string) {
this.formGroup.get("email").setValue(value);
}
async ngOnInit() {
await this.auth(this.data);
}
auth(authResponse: AuthResponse<TwoFactorEmailResponse>) {
super.auth(authResponse);
return this.processResponse(authResponse.response);
}
submit() {
submit = async () => {
if (this.enabled) {
return super.disable(this.formPromise);
await this.disableEmail();
this.onChangeStatus.emit(false);
} else {
return this.enable();
await this.enable();
this.onChangeStatus.emit(true);
}
};
private disableEmail() {
return super.disable(this.formPromise);
}
async sendEmail() {
try {
const request = await this.buildRequestModel(TwoFactorEmailRequest);
request.email = this.email;
this.emailPromise = this.apiService.postTwoFactorEmailSetup(request);
await this.emailPromise;
this.sentEmail = this.email;
} catch (e) {
this.logService.error(e);
}
}
sendEmail = async () => {
const request = await this.buildRequestModel(TwoFactorEmailRequest);
request.email = this.email;
this.emailPromise = this.apiService.postTwoFactorEmailSetup(request);
await this.emailPromise;
this.sentEmail = this.email;
};
protected async enable() {
const request = await this.buildRequestModel(UpdateTwoFactorEmailRequest);
@ -86,6 +112,10 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
});
}
onClose = () => {
this.dialogRef.close(this.enabled);
};
private async processResponse(response: TwoFactorEmailResponse) {
this.token = null;
this.email = response.email;
@ -96,4 +126,15 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
);
}
}
/**
* Strongly typed helper to open a TwoFactorEmailComponentComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
static open(
dialogService: DialogService,
config: DialogConfig<AuthResponse<TwoFactorEmailResponse>>,
) {
return dialogService.open<boolean>(TwoFactorEmailComponent, config);
}
}

View File

@ -1,3 +1,4 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
import { firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
@ -178,11 +179,14 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
if (!result) {
return;
}
const emailComp = await this.openModal(this.emailModalRef, TwoFactorEmailComponent);
await emailComp.auth(result);
emailComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Email);
const authComp: DialogRef<boolean, any> = TwoFactorEmailComponent.open(this.dialogService, {
data: result,
});
authComp.componentInstance.onChangeStatus
.pipe(takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Email);
});
break;
}
case TwoFactorProviderType.WebAuthn: {