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:
parent
1cec69e377
commit
6e55733873
|
@ -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>
|
||||||
|
|
|
@ -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(["/"]);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user