1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-22 11:45:59 +01:00

[SG-656] Fix Trial Initiation Captcha Issue (#3481)

* [refactor] Isolate form validation logic

* [refactor] Relocate a few input scrubbing lines

* [refactor] Isolate RegisterRequest object construction logic

* [refactor] Isolate account registration logic

* [refactor] Isolate login logic

* [fix] Check for captchas during login from trial initiation

* [fix] Avoid a duplicated toast if the account was already created
This commit is contained in:
Addison Beck 2022-09-09 14:56:36 -04:00 committed by GitHub
parent d4581b0ba3
commit 65641a38b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 145 additions and 98 deletions

View File

@ -113,16 +113,21 @@
</div>
<div class="tw-mb-3 tw-flex">
<bit-submit-button [loading]="form.loading">{{ "createAccount" | i18n }}</bit-submit-button>
<a
bitButton
buttonType="secondary"
routerLink="/login"
class="tw-ml-3 tw-inline-flex tw-items-center tw-px-3"
>
<i class="bwi bwi-sign-in tw-mr-2"></i>
{{ "logIn" | i18n }}
</a>
<ng-container *ngIf="!accountCreated">
<bit-submit-button [loading]="form.loading">{{ "createAccount" | i18n }}</bit-submit-button>
<a
bitButton
buttonType="secondary"
routerLink="/login"
class="tw-ml-3 tw-inline-flex tw-items-center tw-px-3"
>
<i class="bwi bwi-sign-in tw-mr-2"></i>
{{ "logIn" | i18n }}
</a>
</ng-container>
<ng-container *ngIf="accountCreated">
<bit-submit-button [loading]="form.loading">{{ "logIn" | i18n }}</bit-submit-button>
</ng-container>
</div>
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
</div>

View File

@ -68,6 +68,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
protected successRoute = "login";
protected accountCreated = false;
constructor(
protected formValidationErrorService: FormValidationErrorsService,
protected formBuilder: UntypedFormBuilder,
@ -92,100 +94,33 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
async submit(showToast = true) {
let email = this.formGroup.get("email")?.value;
let name = this.formGroup.get("name")?.value;
const masterPassword = this.formGroup.get("masterPassword")?.value;
const hint = this.formGroup.get("hint")?.value;
this.formGroup.markAllAsTouched();
this.showErrorSummary = true;
if (this.formGroup.get("acceptPolicies").hasError("required")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesRequired")
);
return;
}
//web
if (this.formGroup.invalid && !showToast) {
return;
}
//desktop, browser
if (this.formGroup.invalid && showToast) {
const errorText = this.getErrorToastMessage();
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
return;
}
if (this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return;
}
}
name = name === "" ? null : name;
email = email.trim().toLowerCase();
const kdf = DEFAULT_KDF_TYPE;
const kdfIterations = DEFAULT_KDF_ITERATIONS;
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(
email,
name,
hashedPassword,
hint,
encKey[1].encryptedString,
kdf,
kdfIterations,
this.referenceData,
this.captchaToken
);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
let name = this.formGroup.get("name")?.value;
name = name === "" ? null : name; // Why do we do this?
const masterPassword = this.formGroup.get("masterPassword")?.value;
try {
this.formPromise = this.apiService.postRegister(request);
try {
await this.formPromise;
} catch (e) {
if (this.handleCaptchaRequired(e)) {
if (!this.accountCreated) {
const registerResponse = await this.registerAccount(
await this.buildRegisterRequest(email, masterPassword, name),
showToast
);
if (registerResponse.captchaRequired) {
return;
} else {
throw e;
}
this.accountCreated = true;
}
if (this.isInTrialFlow) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("trialAccountCreated")
);
//login user here
const credentials = new PasswordLogInCredentials(
email,
masterPassword,
this.captchaToken,
null
);
await this.authService.logIn(credentials);
if (!this.accountCreated) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("trialAccountCreated")
);
}
const loginResponse = await this.logIn(email, masterPassword, this.captchaToken);
if (loginResponse.captchaRequired) {
return;
}
this.createdAccount.emit(this.formGroup.get("email")?.value);
} else {
this.platformUtilsService.showToast(
@ -247,4 +182,111 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
return !ctrlValue && this.showTerms ? { required: true } : null;
};
}
private async validateRegistration(showToast: boolean) {
this.formGroup.markAllAsTouched();
this.showErrorSummary = true;
if (this.formGroup.get("acceptPolicies").hasError("required")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesRequired")
);
return;
}
//web
if (this.formGroup.invalid && !showToast) {
return;
}
//desktop, browser
if (this.formGroup.invalid && showToast) {
const errorText = this.getErrorToastMessage();
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
return;
}
if (this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return;
}
}
}
private async buildRegisterRequest(
email: string,
masterPassword: string,
name: string
): Promise<RegisterRequest> {
const hint = this.formGroup.get("hint")?.value;
const kdf = DEFAULT_KDF_TYPE;
const kdfIterations = DEFAULT_KDF_ITERATIONS;
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(
email,
name,
hashedPassword,
hint,
encKey[1].encryptedString,
kdf,
kdfIterations,
this.referenceData,
this.captchaToken
);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
return request;
}
private async registerAccount(
request: RegisterRequest,
showToast: boolean
): Promise<{ captchaRequired: boolean }> {
await this.validateRegistration(showToast);
this.formPromise = this.apiService.postRegister(request);
try {
await this.formPromise;
return { captchaRequired: false };
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return { captchaRequired: true };
} else {
throw e;
}
}
}
private async logIn(
email: string,
masterPassword: string,
captchaBypassToken: string
): Promise<{ captchaRequired: boolean }> {
const credentials = new PasswordLogInCredentials(
email,
masterPassword,
captchaBypassToken,
null
);
const loginResponse = await this.authService.logIn(credentials);
if (this.handleCaptchaRequired(loginResponse)) {
return { captchaRequired: true };
}
return { captchaRequired: false };
}
}