From 1fcba789050495d6e3f6c99e2f5dc1391afda876 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Thu, 15 Sep 2022 16:46:58 -0400 Subject: [PATCH] [SG-656] Use a captcha bypass during registration (#3531) * Use a captcha bypass during registration The trial initiation flow has a registration step that automatically does a login in the background. This has Captcha problems, namely that it can spawn two captchas in a row - one during registration and one during login. This is not ideal UX, so we've added a bypass token that returns from the registration endpoint that can be used to skip the next captcha. * [review] Introduce ICaptcheProtectedResponse --- libs/angular/src/components/register.component.ts | 14 +++++++++----- libs/common/src/abstractions/api.service.ts | 3 ++- .../authentication/ICaptchaProtectedResponse.ts | 3 +++ .../response/authentication/registerResponse.ts | 12 ++++++++++++ libs/common/src/services/api.service.ts | 8 +++++--- 5 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 libs/common/src/models/response/authentication/ICaptchaProtectedResponse.ts create mode 100644 libs/common/src/models/response/authentication/registerResponse.ts diff --git a/libs/angular/src/components/register.component.ts b/libs/angular/src/components/register.component.ts index 5abf4d9fdb..5d0e708d81 100644 --- a/libs/angular/src/components/register.component.ts +++ b/libs/angular/src/components/register.component.ts @@ -21,6 +21,7 @@ import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/logInC import { KeysRequest } from "@bitwarden/common/models/request/keysRequest"; import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenceEventRequest"; import { RegisterRequest } from "@bitwarden/common/models/request/registerRequest"; +import { RegisterResponse } from "@bitwarden/common/models/response/authentication/registerResponse"; import { PasswordColorText } from "../shared/components/password-strength/password-strength.component"; @@ -32,7 +33,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn @Output() createdAccount = new EventEmitter(); showPassword = false; - formPromise: Promise; + formPromise: Promise; referenceData: ReferenceEventRequest; showTerms = true; showErrorSummary = false; @@ -70,6 +71,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn protected accountCreated = false; + protected captchaBypassToken: string = null; + constructor( protected formValidationErrorService: FormValidationErrorsService, protected formBuilder: UntypedFormBuilder, @@ -107,6 +110,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn if (!registerResponse.successful) { return; } + this.captchaBypassToken = registerResponse.captchaBypassToken; this.accountCreated = true; } if (this.isInTrialFlow) { @@ -117,7 +121,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn this.i18nService.t("trialAccountCreated") ); } - const loginResponse = await this.logIn(email, masterPassword, this.captchaToken); + const loginResponse = await this.logIn(email, masterPassword, this.captchaBypassToken); if (loginResponse.captchaRequired) { return; } @@ -258,14 +262,14 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn private async registerAccount( request: RegisterRequest, showToast: boolean - ): Promise<{ successful: boolean }> { + ): Promise<{ successful: boolean; captchaBypassToken?: string }> { if (!(await this.validateRegistration(showToast)).isValid) { return { successful: false }; } this.formPromise = this.apiService.postRegister(request); try { - await this.formPromise; - return { successful: true }; + const response = await this.formPromise; + return { successful: true, captchaBypassToken: response.captchaBypassToken }; } catch (e) { if (this.handleCaptchaRequired(e)) { return { successful: false }; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 3a661274cc..f08e5c34af 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -84,6 +84,7 @@ import { VerifyEmailRequest } from "../models/request/verifyEmailRequest"; import { ApiKeyResponse } from "../models/response/apiKeyResponse"; import { AttachmentResponse } from "../models/response/attachmentResponse"; import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; +import { RegisterResponse } from "../models/response/authentication/registerResponse"; import { BillingHistoryResponse } from "../models/response/billingHistoryResponse"; import { BillingPaymentResponse } from "../models/response/billingPaymentResponse"; import { BreachAccountResponse } from "../models/response/breachAccountResponse"; @@ -189,7 +190,7 @@ export abstract class ApiService { postSecurityStamp: (request: SecretVerificationRequest) => Promise; getAccountRevisionDate: () => Promise; postPasswordHint: (request: PasswordHintRequest) => Promise; - postRegister: (request: RegisterRequest) => Promise; + postRegister: (request: RegisterRequest) => Promise; postPremium: (data: FormData) => Promise; postIapCheck: (request: IapCheckRequest) => Promise; postReinstatePremium: () => Promise; diff --git a/libs/common/src/models/response/authentication/ICaptchaProtectedResponse.ts b/libs/common/src/models/response/authentication/ICaptchaProtectedResponse.ts new file mode 100644 index 0000000000..3f34b71479 --- /dev/null +++ b/libs/common/src/models/response/authentication/ICaptchaProtectedResponse.ts @@ -0,0 +1,3 @@ +export interface ICaptchaProtectedResponse { + captchaBypassToken: string; +} diff --git a/libs/common/src/models/response/authentication/registerResponse.ts b/libs/common/src/models/response/authentication/registerResponse.ts new file mode 100644 index 0000000000..b9f5f152be --- /dev/null +++ b/libs/common/src/models/response/authentication/registerResponse.ts @@ -0,0 +1,12 @@ +import { BaseResponse } from "../baseResponse"; + +import { ICaptchaProtectedResponse } from "./ICaptchaProtectedResponse"; + +export class RegisterResponse extends BaseResponse implements ICaptchaProtectedResponse { + captchaBypassToken: string; + + constructor(response: any) { + super(response); + this.captchaBypassToken = this.getResponseProperty("CaptchaBypassToken"); + } +} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index b1254b1213..dbc67f5727 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -92,6 +92,7 @@ import { VerifyEmailRequest } from "../models/request/verifyEmailRequest"; import { ApiKeyResponse } from "../models/response/apiKeyResponse"; import { AttachmentResponse } from "../models/response/attachmentResponse"; import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; +import { RegisterResponse } from "../models/response/authentication/registerResponse"; import { BillingHistoryResponse } from "../models/response/billingHistoryResponse"; import { BillingPaymentResponse } from "../models/response/billingPaymentResponse"; import { BreachAccountResponse } from "../models/response/breachAccountResponse"; @@ -337,17 +338,18 @@ export class ApiService implements ApiServiceAbstraction { return this.send("POST", "/accounts/password-hint", request, false, false); } - postRegister(request: RegisterRequest): Promise { - return this.send( + async postRegister(request: RegisterRequest): Promise { + const r = await this.send( "POST", "/accounts/register", request, false, - false, + true, this.platformUtilsService.isDev() ? this.environmentService.getIdentityUrl() : this.environmentService.getApiUrl() ); + return new RegisterResponse(r); } async postPremium(data: FormData): Promise {