diff --git a/apps/web/src/app/auth/recover-two-factor.component.html b/apps/web/src/app/auth/recover-two-factor.component.html index 11d281b742..e364176580 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.html +++ b/apps/web/src/app/auth/recover-two-factor.component.html @@ -1,76 +1,40 @@ -
-
-
-

{{ "recoverAccountTwoStep" | i18n }}

-
-
-

- {{ "recoverAccountTwoStepDesc" | i18n }} - {{ "learnMore" | i18n }} -

-
- - -
-
- - -
-
- - -
-
-
- - - {{ "cancel" | i18n }} - -
-
-
-
+ +

+ {{ "recoverAccountTwoStepDesc" | i18n }} + {{ "learnMore" | i18n }} +

+ + {{ "emailAddress" | i18n }} + + + + {{ "masterPass" | i18n }} + + + + {{ "recoveryCodeTitle" | i18n }} + + +
+
+ + + {{ "cancel" | i18n }} +
diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 145c46c8df..4996dbe0a5 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -1,4 +1,5 @@ import { Component } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; @@ -6,7 +7,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorRecoveryRequest } from "@bitwarden/common/auth/models/request/two-factor-recovery.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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"; @Component({ @@ -14,10 +14,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl templateUrl: "recover-two-factor.component.html", }) export class RecoverTwoFactorComponent { - email: string; - masterPassword: string; - recoveryCode: string; - formPromise: Promise; + protected formGroup = new FormGroup({ + email: new FormControl(null, [Validators.required]), + masterPassword: new FormControl(null, [Validators.required]), + recoveryCode: new FormControl(null, [Validators.required]), + }); constructor( private router: Router, @@ -26,31 +27,32 @@ export class RecoverTwoFactorComponent { private i18nService: I18nService, private cryptoService: CryptoService, private loginStrategyService: LoginStrategyServiceAbstraction, - private logService: LogService, ) {} - async submit() { - try { - const request = new TwoFactorRecoveryRequest(); - request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); - request.email = this.email.trim().toLowerCase(); - const key = await this.loginStrategyService.makePreloginKey( - this.masterPassword, - request.email, - ); - request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key); - this.formPromise = this.apiService.postTwoFactorRecover(request); - await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("twoStepRecoverDisabled"), - ); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/"]); - } catch (e) { - this.logService.error(e); - } + get email(): string { + return this.formGroup.value.email; } + + get masterPassword(): string { + return this.formGroup.value.masterPassword; + } + + get recoveryCode(): string { + return this.formGroup.value.recoveryCode; + } + + submit = async () => { + const request = new TwoFactorRecoveryRequest(); + request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); + request.email = this.email.trim().toLowerCase(); + const key = await this.loginStrategyService.makePreloginKey(this.masterPassword, request.email); + request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key); + await this.apiService.postTwoFactorRecover(request); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("twoStepRecoverDisabled"), + ); + await this.router.navigate(["/"]); + }; } diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index c7b4631fa3..8509e987eb 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -7,7 +7,9 @@ import { redirectGuard, tdeDecryptionRequiredGuard, UnauthGuard, + unauthGuardFn, } from "@bitwarden/angular/auth/guards"; +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular"; import { flagEnabled, Flags } from "../utils/flags"; @@ -40,6 +42,7 @@ import { UpdatePasswordComponent } from "./auth/update-password.component"; import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; +import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; import { DataProperties } from "./core"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; @@ -141,12 +144,6 @@ const routes: Routes = [ data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties, }, { path: "recover", pathMatch: "full", redirectTo: "recover-2fa" }, - { - path: "recover-2fa", - component: RecoverTwoFactorComponent, - canActivate: [UnauthGuard], - data: { titleId: "recoverAccountTwoStep" } satisfies DataProperties, - }, { path: "recover-delete", component: RecoverDeleteComponent, @@ -203,6 +200,31 @@ const routes: Routes = [ }, ], }, + { + path: "", + component: AnonLayoutWrapperComponent, + children: [ + { + path: "recover-2fa", + canActivate: [unauthGuardFn()], + children: [ + { + path: "", + component: RecoverTwoFactorComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + data: { + pageTitle: "recoverAccountTwoStep", + titleId: "recoverAccountTwoStep", + } satisfies DataProperties & AnonLayoutWrapperData, + }, + ], + }, { path: "", component: UserLayoutComponent,