mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00: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:
@@ -113,16 +113,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tw-mb-3 tw-flex">
|
<div class="tw-mb-3 tw-flex">
|
||||||
<bit-submit-button [loading]="form.loading">{{ "createAccount" | i18n }}</bit-submit-button>
|
<ng-container *ngIf="!accountCreated">
|
||||||
<a
|
<bit-submit-button [loading]="form.loading">{{ "createAccount" | i18n }}</bit-submit-button>
|
||||||
bitButton
|
<a
|
||||||
buttonType="secondary"
|
bitButton
|
||||||
routerLink="/login"
|
buttonType="secondary"
|
||||||
class="tw-ml-3 tw-inline-flex tw-items-center tw-px-3"
|
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 }}
|
<i class="bwi bwi-sign-in tw-mr-2"></i>
|
||||||
</a>
|
{{ "logIn" | i18n }}
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="accountCreated">
|
||||||
|
<bit-submit-button [loading]="form.loading">{{ "logIn" | i18n }}</bit-submit-button>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
|||||||
|
|
||||||
protected successRoute = "login";
|
protected successRoute = "login";
|
||||||
|
|
||||||
|
protected accountCreated = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected formValidationErrorService: FormValidationErrorsService,
|
protected formValidationErrorService: FormValidationErrorsService,
|
||||||
protected formBuilder: UntypedFormBuilder,
|
protected formBuilder: UntypedFormBuilder,
|
||||||
@@ -92,100 +94,33 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
|||||||
|
|
||||||
async submit(showToast = true) {
|
async submit(showToast = true) {
|
||||||
let email = this.formGroup.get("email")?.value;
|
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();
|
email = email.trim().toLowerCase();
|
||||||
const kdf = DEFAULT_KDF_TYPE;
|
let name = this.formGroup.get("name")?.value;
|
||||||
const kdfIterations = DEFAULT_KDF_ITERATIONS;
|
name = name === "" ? null : name; // Why do we do this?
|
||||||
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
|
const masterPassword = this.formGroup.get("masterPassword")?.value;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.apiService.postRegister(request);
|
if (!this.accountCreated) {
|
||||||
try {
|
const registerResponse = await this.registerAccount(
|
||||||
await this.formPromise;
|
await this.buildRegisterRequest(email, masterPassword, name),
|
||||||
} catch (e) {
|
showToast
|
||||||
if (this.handleCaptchaRequired(e)) {
|
);
|
||||||
|
if (registerResponse.captchaRequired) {
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
this.accountCreated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isInTrialFlow) {
|
if (this.isInTrialFlow) {
|
||||||
this.platformUtilsService.showToast(
|
if (!this.accountCreated) {
|
||||||
"success",
|
this.platformUtilsService.showToast(
|
||||||
null,
|
"success",
|
||||||
this.i18nService.t("trialAccountCreated")
|
null,
|
||||||
);
|
this.i18nService.t("trialAccountCreated")
|
||||||
//login user here
|
);
|
||||||
const credentials = new PasswordLogInCredentials(
|
}
|
||||||
email,
|
const loginResponse = await this.logIn(email, masterPassword, this.captchaToken);
|
||||||
masterPassword,
|
if (loginResponse.captchaRequired) {
|
||||||
this.captchaToken,
|
return;
|
||||||
null
|
}
|
||||||
);
|
|
||||||
await this.authService.logIn(credentials);
|
|
||||||
|
|
||||||
this.createdAccount.emit(this.formGroup.get("email")?.value);
|
this.createdAccount.emit(this.formGroup.get("email")?.value);
|
||||||
} else {
|
} else {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@@ -247,4 +182,111 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
|||||||
return !ctrlValue && this.showTerms ? { required: true } : null;
|
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 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user