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

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
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",
})
export class ChangeEmailComponent implements OnInit {
masterPassword: string;
newEmail: string;
token: string;
tokenSent = 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(
private apiService: ApiService,
@ -32,6 +36,7 @@ export class ChangeEmailComponent implements OnInit {
private messagingService: MessagingService,
private logService: LogService,
private stateService: StateService,
private formBuilder: FormBuilder,
) {}
async ngOnInit() {
@ -41,47 +46,59 @@ export class ChangeEmailComponent implements OnInit {
);
}
async submit() {
this.newEmail = this.newEmail.trim().toLowerCase();
protected submit = async () => {
// 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) {
const request = new EmailTokenRequest();
request.newEmail = this.newEmail;
request.newEmail = newEmail;
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword),
step1Value.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword),
);
try {
this.formPromise = this.apiService.postEmailToken(request);
await this.formPromise;
this.tokenSent = true;
await this.apiService.postEmailToken(request);
this.activateStep2();
} catch (e) {
this.logService.error(e);
}
} else {
const request = new EmailRequest();
request.token = this.token;
request.newEmail = this.newEmail;
request.token = this.formGroup.value.token;
request.newEmail = newEmail;
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword),
step1Value.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword),
);
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.newEmail,
step1Value.masterPassword,
newEmail,
kdf,
kdfConfig,
);
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
step1Value.masterPassword,
newMasterKey,
);
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString;
try {
this.formPromise = this.apiService.postEmail(request);
await this.formPromise;
await this.apiService.postEmail(request);
this.reset();
this.platformUtilsService.showToast(
"success",
@ -93,10 +110,22 @@ export class ChangeEmailComponent implements OnInit {
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() {
this.token = this.newEmail = this.masterPassword = null;
this.formGroup.reset();
this.formGroup.controls.step1.enable();
this.formGroup.controls.token.disable();
this.tokenSent = false;
}
}