mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 23:03:32 +00:00
[PM-4975] Migrate change email to CL (#7223)
This commit is contained in:
@@ -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"
|
||||||
type="password"
|
bitInput
|
||||||
name="MasterPasswordHash"
|
type="password"
|
||||||
class="form-control"
|
formControlName="masterPassword"
|
||||||
[(ngModel)]="masterPassword"
|
/>
|
||||||
required
|
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||||
[readonly]="tokenSent"
|
</bit-form-field>
|
||||||
appInputVerbatim
|
<bit-form-field>
|
||||||
/>
|
<bit-label>{{ "newEmail" | i18n }}</bit-label>
|
||||||
</div>
|
<input
|
||||||
<div class="form-group">
|
id="change-email_input_newEmail"
|
||||||
<label for="newEmail">{{ "newEmail" | i18n }}</label>
|
bitInput
|
||||||
<input
|
type="email"
|
||||||
id="newEmail"
|
formControlName="newEmail"
|
||||||
class="form-control"
|
inputmode="email"
|
||||||
type="text"
|
/>
|
||||||
name="NewEmail"
|
</bit-form-field>
|
||||||
[(ngModel)]="newEmail"
|
|
||||||
required
|
|
||||||
[readonly]="tokenSent"
|
|
||||||
inputmode="email"
|
|
||||||
appInputVerbatim="false"
|
|
||||||
/>
|
|
||||||
</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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user