1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 07:13:32 +00:00

[PM-4975] Migrate change email to CL (#7223)

This commit is contained in:
Oscar Hinton
2024-01-11 15:23:57 +01:00
committed by GitHub
parent 1f57244d1a
commit 48d4c88770
2 changed files with 86 additions and 74 deletions

View File

@@ -1,65 +1,48 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate> <form [formGroup]="formGroup" [bitSubmit]="submit">
<app-callout type="warning" *ngIf="showTwoFactorEmailWarning"> <app-callout type="warning" *ngIf="showTwoFactorEmailWarning">
{{ "changeEmailTwoFactorWarning" | i18n }} {{ "changeEmailTwoFactorWarning" | i18n }}
</app-callout> </app-callout>
<div class="row">
<div class="col-6"> <div class="tw-w-1/2 tw-pr-2" formGroupName="step1">
<div class="form-group"> <bit-form-field>
<label for="masterPassword">{{ "masterPass" | i18n }}</label> <bit-label>{{ "masterPass" | i18n }}</bit-label>
<input <input
id="masterPassword" id="change-email_input_masterPassword"
bitInput
type="password" type="password"
name="MasterPasswordHash" formControlName="masterPassword"
class="form-control"
[(ngModel)]="masterPassword"
required
[readonly]="tokenSent"
appInputVerbatim
/> />
</div> <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<div class="form-group"> </bit-form-field>
<label for="newEmail">{{ "newEmail" | i18n }}</label> <bit-form-field>
<bit-label>{{ "newEmail" | i18n }}</bit-label>
<input <input
id="newEmail" id="change-email_input_newEmail"
class="form-control" bitInput
type="text" type="email"
name="NewEmail" formControlName="newEmail"
[(ngModel)]="newEmail"
required
[readonly]="tokenSent"
inputmode="email" inputmode="email"
appInputVerbatim="false"
/> />
</bit-form-field>
</div> </div>
</div>
</div>
<ng-container *ngIf="tokenSent"> <ng-container *ngIf="tokenSent">
<hr /> <hr />
<p>{{ "changeEmailDesc" | i18n: newEmail }}</p> <p>{{ "changeEmailDesc" | i18n: formGroup.controls.step1.value.newEmail }}</p>
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout> <app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<div class="row">
<div class="col-6"> <div class="tw-w-1/2 tw-pr-2">
<div class="form-group"> <bit-form-field>
<label for="token">{{ "code" | i18n }}</label> <bit-label>{{ "code" | i18n }}</bit-label>
<input <input id="change-email_input_token" bitInput type="text" formControlName="token" />
id="token" </bit-form-field>
class="form-control"
type="text"
name="Token"
[(ngModel)]="token"
required
appInputVerbatim
/>
</div>
</div>
</div> </div>
</ng-container> </ng-container>
<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> <button type="submit" bitButton buttonType="primary" bitFormButton>
<span *ngIf="!tokenSent">{{ "continue" | i18n }}</span> {{ (tokenSent ? "changeEmail" : "continue") | i18n }}
<span *ngIf="tokenSent">{{ "changeEmail" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" *ngIf="tokenSent" (click)="reset()"> <button type="button" bitButton *ngIf="tokenSent" (click)="reset()">
{{ "cancel" | i18n }} {{ "cancel" | i18n }}
</button> </button>
</form> </form>

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -16,13 +17,16 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
templateUrl: "change-email.component.html", templateUrl: "change-email.component.html",
}) })
export class ChangeEmailComponent implements OnInit { export class ChangeEmailComponent implements OnInit {
masterPassword: string;
newEmail: string;
token: string;
tokenSent = false; tokenSent = false;
showTwoFactorEmailWarning = false; showTwoFactorEmailWarning = false;
formPromise: Promise<any>; protected formGroup = this.formBuilder.group({
step1: this.formBuilder.group({
masterPassword: ["", [Validators.required]],
newEmail: ["", [Validators.required, Validators.email]],
}),
token: [{ value: "", disabled: true }, [Validators.required]],
});
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
@@ -32,6 +36,7 @@ export class ChangeEmailComponent implements OnInit {
private messagingService: MessagingService, private messagingService: MessagingService,
private logService: LogService, private logService: LogService,
private stateService: StateService, private stateService: StateService,
private formBuilder: FormBuilder,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -41,47 +46,59 @@ export class ChangeEmailComponent implements OnInit {
); );
} }
async submit() { protected submit = async () => {
this.newEmail = this.newEmail.trim().toLowerCase(); // This form has multiple steps, so we need to mark all the groups as touched.
this.formGroup.controls.step1.markAllAsTouched();
if (this.tokenSent) {
this.formGroup.controls.token.markAllAsTouched();
}
// Exit if the form is invalid.
if (this.formGroup.invalid) {
return;
}
const step1Value = this.formGroup.controls.step1.value;
const newEmail = step1Value.newEmail.trim().toLowerCase();
if (!this.tokenSent) { if (!this.tokenSent) {
const request = new EmailTokenRequest(); const request = new EmailTokenRequest();
request.newEmail = this.newEmail; request.newEmail = newEmail;
request.masterPasswordHash = await this.cryptoService.hashMasterKey( request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword, step1Value.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword), await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword),
); );
try { try {
this.formPromise = this.apiService.postEmailToken(request); await this.apiService.postEmailToken(request);
await this.formPromise; this.activateStep2();
this.tokenSent = true;
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} else { } else {
const request = new EmailRequest(); const request = new EmailRequest();
request.token = this.token; request.token = this.formGroup.value.token;
request.newEmail = this.newEmail; request.newEmail = newEmail;
request.masterPasswordHash = await this.cryptoService.hashMasterKey( request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword, step1Value.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword), await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword),
); );
const kdf = await this.stateService.getKdfType(); const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig(); const kdfConfig = await this.stateService.getKdfConfig();
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword, step1Value.masterPassword,
this.newEmail, newEmail,
kdf, kdf,
kdfConfig, kdfConfig,
); );
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey( request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword, step1Value.masterPassword,
newMasterKey, newMasterKey,
); );
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey); const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString; request.key = newUserKey[1].encryptedString;
try { try {
this.formPromise = this.apiService.postEmail(request); await this.apiService.postEmail(request);
await this.formPromise;
this.reset(); this.reset();
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
@@ -93,10 +110,22 @@ export class ChangeEmailComponent implements OnInit {
this.logService.error(e); this.logService.error(e);
} }
} }
};
// Disable step1 and enable token
activateStep2() {
this.formGroup.controls.step1.disable();
this.formGroup.controls.token.enable();
this.tokenSent = true;
} }
// Reset form and re-enable step1
reset() { reset() {
this.token = this.newEmail = this.masterPassword = null; this.formGroup.reset();
this.formGroup.controls.step1.enable();
this.formGroup.controls.token.disable();
this.tokenSent = false; this.tokenSent = false;
} }
} }