1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-08-27 23:31:41 +02:00

PM-4951 Migrate Recover Two Factor Component (#9170)

* PM-4951 Migrate Recover Two Factor Component

* PM-4951 Addressed review comments

* PM-4951 Addressed review comments

* update route

* add type safety to data properties

---------

Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com>
This commit is contained in:
KiruthigaManivannan 2024-06-05 23:22:04 +05:30 committed by GitHub
parent 1cec69e377
commit 6e55733873
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 97 additions and 109 deletions

View File

@ -1,76 +1,40 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form [formGroup]="formGroup" [bitSubmit]="submit">
<div class="row justify-content-md-center mt-5"> <p bitTypography="body1">
<div class="col-5"> {{ "recoverAccountTwoStepDesc" | i18n }}
<p class="lead text-center mb-4">{{ "recoverAccountTwoStep" | i18n }}</p> <a
<div class="card"> bitLink
<div class="card-body"> href="https://bitwarden.com/help/lost-two-step-device/"
<p> target="_blank"
{{ "recoverAccountTwoStepDesc" | i18n }} rel="noreferrer"
<a >{{ "learnMore" | i18n }}</a
href="https://bitwarden.com/help/lost-two-step-device/" >
target="_blank" </p>
rel="noreferrer" <bit-form-field>
>{{ "learnMore" | i18n }}</a <bit-label>{{ "emailAddress" | i18n }}</bit-label>
> <input
</p> bitInput
<div class="form-group"> type="text"
<label for="email">{{ "emailAddress" | i18n }}</label> formControlName="email"
<input appAutofocus
id="email" inputmode="email"
class="form-control" appInputVerbatim="false"
type="text" />
name="Email" </bit-form-field>
[(ngModel)]="email" <bit-form-field>
required <bit-label>{{ "masterPass" | i18n }}</bit-label>
appAutofocus <input bitInput type="password" formControlName="masterPassword" appInputVerbatim />
inputmode="email" </bit-form-field>
appInputVerbatim="false" <bit-form-field>
/> <bit-label>{{ "recoveryCodeTitle" | i18n }}</bit-label>
</div> <input bitInput type="text" formControlName="recoveryCode" appInputVerbatim />
<div class="form-group"> </bit-form-field>
<label for="masterPassword">{{ "masterPass" | i18n }}</label> <hr />
<input <div class="tw-flex tw-gap-2">
id="masterPassword" <button type="submit" bitButton bitFormButton buttonType="primary" [block]="true">
type="password" {{ "submit" | i18n }}
name="MasterPassword" </button>
class="form-control" <a bitButton buttonType="secondary" routerLink="/login" [block]="true">
[(ngModel)]="masterPassword" {{ "cancel" | i18n }}
required </a>
appInputVerbatim
/>
</div>
<div class="form-group">
<label for="recoveryCode">{{ "recoveryCodeTitle" | i18n }}</label>
<input
id="recoveryCode"
class="text-monospace form-control"
type="text"
name="RecoveryCode"
[(ngModel)]="recoveryCode"
required
appInputVerbatim
/>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "submit" | i18n }}</span>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div> </div>
</form> </form>

View File

@ -1,4 +1,5 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; 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 { TwoFactorRecoveryRequest } from "@bitwarden/common/auth/models/request/two-factor-recovery.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@Component({ @Component({
@ -14,10 +14,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
templateUrl: "recover-two-factor.component.html", templateUrl: "recover-two-factor.component.html",
}) })
export class RecoverTwoFactorComponent { export class RecoverTwoFactorComponent {
email: string; protected formGroup = new FormGroup({
masterPassword: string; email: new FormControl(null, [Validators.required]),
recoveryCode: string; masterPassword: new FormControl(null, [Validators.required]),
formPromise: Promise<any>; recoveryCode: new FormControl(null, [Validators.required]),
});
constructor( constructor(
private router: Router, private router: Router,
@ -26,31 +27,32 @@ export class RecoverTwoFactorComponent {
private i18nService: I18nService, private i18nService: I18nService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private loginStrategyService: LoginStrategyServiceAbstraction, private loginStrategyService: LoginStrategyServiceAbstraction,
private logService: LogService,
) {} ) {}
async submit() { get email(): string {
try { return this.formGroup.value.email;
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 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(["/"]);
};
} }

View File

@ -7,7 +7,9 @@ import {
redirectGuard, redirectGuard,
tdeDecryptionRequiredGuard, tdeDecryptionRequiredGuard,
UnauthGuard, UnauthGuard,
unauthGuardFn,
} from "@bitwarden/angular/auth/guards"; } from "@bitwarden/angular/auth/guards";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular";
import { flagEnabled, Flags } from "../utils/flags"; 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 { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component";
import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component";
import { DataProperties } from "./core"; import { DataProperties } from "./core";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component";
@ -141,12 +144,6 @@ const routes: Routes = [
data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties, data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties,
}, },
{ path: "recover", pathMatch: "full", redirectTo: "recover-2fa" }, { path: "recover", pathMatch: "full", redirectTo: "recover-2fa" },
{
path: "recover-2fa",
component: RecoverTwoFactorComponent,
canActivate: [UnauthGuard],
data: { titleId: "recoverAccountTwoStep" } satisfies DataProperties,
},
{ {
path: "recover-delete", path: "recover-delete",
component: RecoverDeleteComponent, 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: "", path: "",
component: UserLayoutComponent, component: UserLayoutComponent,