[PM-7084] 4/6: Introduce shared email two-factor component (#9770)

* Add shared email two-factor component

* Update apps/browser/src/auth/popup/two-factor-auth-email.component.ts

Co-authored-by: Jake Fink <jfink@bitwarden.com>

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
Bernd Schoolmann 2024-07-15 15:59:39 +02:00 committed by GitHub
parent a1667e5603
commit 5a46c7d5cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 196 additions and 0 deletions

View File

@ -0,0 +1,55 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, inject } from "@angular/core";
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
import { TwoFactorAuthEmailComponent as TwoFactorAuthEmailBaseComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-email.component";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions";
import { ButtonModule } from "../../../../../libs/components/src/button";
import { DialogService } from "../../../../../libs/components/src/dialog";
import { FormFieldModule } from "../../../../../libs/components/src/form-field";
import { LinkModule } from "../../../../../libs/components/src/link";
import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe";
import { TypographyModule } from "../../../../../libs/components/src/typography";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
@Component({
standalone: true,
selector: "app-two-factor-auth-email",
templateUrl:
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.html",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
FormsModule,
],
providers: [I18nPipe],
})
export class TwoFactorAuthEmailComponent extends TwoFactorAuthEmailBaseComponent {
private dialogService = inject(DialogService);
async ngOnInit(): Promise<void> {
if (BrowserPopupUtils.inPopup(window)) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "warning" },
content: { key: "popup2faCloseMessage" },
type: "warning",
});
if (confirmed) {
await BrowserPopupUtils.openCurrentPagePopout(window);
return;
}
}
await super.ngOnInit();
}
}

View File

@ -41,6 +41,8 @@ import {
import { BrowserApi } from "../../platform/browser/browser-api";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
@Component({
standalone: true,
templateUrl:
@ -59,6 +61,7 @@ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthEmailComponent,
TwoFactorAuthAuthenticatorComponent,
TwoFactorAuthYubikeyComponent,
],

View File

@ -5,6 +5,7 @@ import { ReactiveFormsModule } from "@angular/forms";
import { RouterLink } from "@angular/router";
import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthEmailComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component";
import { TwoFactorAuthYubikeyComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
@ -35,6 +36,7 @@ import { TypographyModule } from "../../../../libs/components/src/typography";
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthEmailComponent,
TwoFactorAuthAuthenticatorComponent,
TwoFactorAuthYubikeyComponent,
],

View File

@ -20,6 +20,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { LinkModule, TypographyModule, CheckboxModule, DialogService } from "@bitwarden/components";
import { TwoFactorAuthAuthenticatorComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthEmailComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component";
import { TwoFactorAuthYubikeyComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
@ -50,6 +51,7 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field";
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthEmailComponent,
TwoFactorAuthAuthenticatorComponent,
TwoFactorAuthYubikeyComponent,
],

View File

@ -0,0 +1,19 @@
<p bitTypography="body1">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p>
<bit-form-field>
<bit-label>{{ "verificationCode" | i18n }}</bit-label>
<input
bitInput
type="text"
appAutofocus
appInputVerbatim
[(ngModel)]="tokenValue"
(input)="token.emit(tokenValue)"
/>
<bit-hint>
<a bitLink href="#" appStopClick (click)="sendEmail(true)">
{{ "sendVerificationCodeEmailAgain" | i18n }}
</a></bit-hint
>
</bit-form-field>

View File

@ -0,0 +1,109 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Output } from "@angular/core";
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
ButtonModule,
LinkModule,
TypographyModule,
FormFieldModule,
AsyncActionsModule,
} from "@bitwarden/components";
@Component({
standalone: true,
selector: "app-two-factor-auth-email",
templateUrl: "two-factor-auth-email.component.html",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
FormsModule,
],
providers: [I18nPipe],
})
export class TwoFactorAuthEmailComponent {
@Output() token = new EventEmitter<string>();
twoFactorEmail: string = null;
emailPromise: Promise<any>;
tokenValue: string = "";
constructor(
protected i18nService: I18nService,
protected twoFactorService: TwoFactorService,
protected loginStrategyService: LoginStrategyServiceAbstraction,
protected platformUtilsService: PlatformUtilsService,
protected logService: LogService,
protected apiService: ApiService,
protected appIdService: AppIdService,
) {}
async ngOnInit(): Promise<void> {
const providerData = await this.twoFactorService.getProviders().then((providers) => {
return providers.get(TwoFactorProviderType.Email);
});
this.twoFactorEmail = providerData.Email;
if ((await this.twoFactorService.getProviders()).size > 1) {
await this.sendEmail(false);
}
}
async sendEmail(doToast: boolean) {
if (this.emailPromise != null) {
return;
}
if ((await this.loginStrategyService.getEmail()) == null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sessionTimeout"),
);
return;
}
try {
const request = new TwoFactorEmailRequest();
request.email = await this.loginStrategyService.getEmail();
request.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash();
request.ssoEmail2FaSessionToken =
await this.loginStrategyService.getSsoEmail2FaSessionToken();
request.deviceIdentifier = await this.appIdService.getAppId();
request.authRequestAccessCode = await this.loginStrategyService.getAccessCode();
request.authRequestId = await this.loginStrategyService.getAuthRequestId();
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
);
}
} catch (e) {
this.logService.error(e);
}
this.emailPromise = null;
}
}

View File

@ -1,5 +1,9 @@
<form [bitSubmit]="submitForm" [formGroup]="formGroup" autocomplete="off">
<div class="tw-min-w-96">
<app-two-factor-auth-email
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Email"
/>
<app-two-factor-auth-authenticator
(token)="token = $event"
*ngIf="selectedProviderType === providerType.Authenticator"

View File

@ -39,6 +39,7 @@ import {
import { CaptchaProtectedComponent } from "../captcha-protected.component";
import { TwoFactorAuthAuthenticatorComponent } from "./two-factor-auth-authenticator.component";
import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
import { TwoFactorAuthYubikeyComponent } from "./two-factor-auth-yubikey.component";
import {
TwoFactorOptionsDialogResult,
@ -60,6 +61,7 @@ import {
ButtonModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
TwoFactorAuthEmailComponent,
TwoFactorAuthYubikeyComponent,
],
providers: [I18nPipe],