mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[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:
@@ -1,101 +1,53 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="2faEmailTitle">
|
<form [formGroup]="formGroup" [bitSubmit]="submit" *ngIf="authed">
|
||||||
<div class="modal-dialog" role="document">
|
<bit-dialog>
|
||||||
<div class="modal-content">
|
<span bitDialogTitle>
|
||||||
<div class="modal-header">
|
{{ "twoStepLogin" | i18n }}
|
||||||
<h1 class="modal-title" id="2faEmailTitle">
|
<span bitTypography="body1">{{ "emailTitle" | i18n }}</span>
|
||||||
{{ "twoStepLogin" | i18n }}
|
</span>
|
||||||
<small>{{ "emailTitle" | i18n }}</small>
|
<ng-container bitDialogContent>
|
||||||
</h1>
|
<ng-container *ngIf="enabled">
|
||||||
<button
|
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
||||||
type="button"
|
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||||
class="close"
|
</app-callout>
|
||||||
data-dismiss="modal"
|
<strong>{{ "email" | i18n }}:</strong> {{ email }}
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
</ng-container>
|
||||||
>
|
<ng-container *ngIf="!enabled">
|
||||||
<span aria-hidden="true">×</span>
|
<p class="tw-flex">
|
||||||
</button>
|
<span class="tw-mr-3">{{ "twoFactorEmailDesc" | i18n }}</span>
|
||||||
</div>
|
<img class="tw-float-right tw-ml-auto mfaType1" alt="Email logo" />
|
||||||
<form
|
</p>
|
||||||
#form
|
<bit-form-field>
|
||||||
(ngSubmit)="submit()"
|
<bit-label>1. {{ "twoFactorEmailEnterEmail" | i18n }}</bit-label>
|
||||||
[appApiAction]="formPromise"
|
<input
|
||||||
ngNativeValidate
|
bitInput
|
||||||
*ngIf="authed"
|
type="text"
|
||||||
>
|
formControlName="email"
|
||||||
<div class="modal-body">
|
inputmode="email"
|
||||||
<ng-container *ngIf="enabled">
|
appInputVerbatim="false"
|
||||||
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
/>
|
||||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
</bit-form-field>
|
||||||
</app-callout>
|
<div class="tw-mb-3 tw-flex">
|
||||||
<strong>{{ "email" | i18n }}:</strong> {{ email }}
|
<button bitButton type="button" buttonType="primary" [bitAction]="sendEmail">
|
||||||
</ng-container>
|
{{ "sendEmail" | i18n }}
|
||||||
<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 }}
|
|
||||||
</button>
|
</button>
|
||||||
|
<span class="tw-text-success tw-ml-3" *ngIf="sentEmail">
|
||||||
|
{{ "verificationCodeEmailSent" | i18n: sentEmail }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<bit-form-field>
|
||||||
</div>
|
<bit-label>2. {{ "twoFactorEmailEnterCode" | i18n }}</bit-label>
|
||||||
</div>
|
<input bitInput type="text" formControlName="token" appInputVerbatim />
|
||||||
</div>
|
</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>
|
||||||
|
|||||||
@@ -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 { firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
@@ -19,18 +21,22 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component";
|
|||||||
@Component({
|
@Component({
|
||||||
selector: "app-two-factor-email",
|
selector: "app-two-factor-email",
|
||||||
templateUrl: "two-factor-email.component.html",
|
templateUrl: "two-factor-email.component.html",
|
||||||
|
outputs: ["onUpdated"],
|
||||||
})
|
})
|
||||||
export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
|
export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
|
||||||
|
@Output() onChangeStatus: EventEmitter<boolean> = new EventEmitter();
|
||||||
type = TwoFactorProviderType.Email;
|
type = TwoFactorProviderType.Email;
|
||||||
email: string;
|
|
||||||
token: string;
|
|
||||||
sentEmail: string;
|
sentEmail: string;
|
||||||
formPromise: Promise<TwoFactorEmailResponse>;
|
formPromise: Promise<TwoFactorEmailResponse>;
|
||||||
emailPromise: Promise<unknown>;
|
emailPromise: Promise<unknown>;
|
||||||
|
|
||||||
override componentName = "app-two-factor-email";
|
override componentName = "app-two-factor-email";
|
||||||
|
formGroup = this.formBuilder.group({
|
||||||
|
token: [null],
|
||||||
|
email: ["", [Validators.email, Validators.required]],
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorEmailResponse>,
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
@@ -38,6 +44,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
|
|||||||
userVerificationService: UserVerificationService,
|
userVerificationService: UserVerificationService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private dialogRef: DialogRef,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
apiService,
|
apiService,
|
||||||
@@ -48,31 +56,49 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
|
|||||||
dialogService,
|
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>) {
|
auth(authResponse: AuthResponse<TwoFactorEmailResponse>) {
|
||||||
super.auth(authResponse);
|
super.auth(authResponse);
|
||||||
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.disableEmail();
|
||||||
|
this.onChangeStatus.emit(false);
|
||||||
} else {
|
} else {
|
||||||
return this.enable();
|
await this.enable();
|
||||||
|
this.onChangeStatus.emit(true);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private disableEmail() {
|
||||||
|
return super.disable(this.formPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEmail() {
|
sendEmail = async () => {
|
||||||
try {
|
const request = await this.buildRequestModel(TwoFactorEmailRequest);
|
||||||
const request = await this.buildRequestModel(TwoFactorEmailRequest);
|
request.email = this.email;
|
||||||
request.email = this.email;
|
this.emailPromise = this.apiService.postTwoFactorEmailSetup(request);
|
||||||
this.emailPromise = this.apiService.postTwoFactorEmailSetup(request);
|
await this.emailPromise;
|
||||||
await this.emailPromise;
|
this.sentEmail = this.email;
|
||||||
this.sentEmail = this.email;
|
};
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async enable() {
|
protected async enable() {
|
||||||
const request = await this.buildRequestModel(UpdateTwoFactorEmailRequest);
|
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) {
|
private async processResponse(response: TwoFactorEmailResponse) {
|
||||||
this.token = null;
|
this.token = null;
|
||||||
this.email = response.email;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { DialogRef } from "@angular/cdk/dialog";
|
||||||
import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
|
import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
import { firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
import { firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
@@ -178,11 +179,14 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const emailComp = await this.openModal(this.emailModalRef, TwoFactorEmailComponent);
|
const authComp: DialogRef<boolean, any> = TwoFactorEmailComponent.open(this.dialogService, {
|
||||||
await emailComp.auth(result);
|
data: result,
|
||||||
emailComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
|
|
||||||
this.updateStatus(enabled, TwoFactorProviderType.Email);
|
|
||||||
});
|
});
|
||||||
|
authComp.componentInstance.onChangeStatus
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((enabled: boolean) => {
|
||||||
|
this.updateStatus(enabled, TwoFactorProviderType.Email);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TwoFactorProviderType.WebAuthn: {
|
case TwoFactorProviderType.WebAuthn: {
|
||||||
|
|||||||
Reference in New Issue
Block a user