Skip to content

Commit

Permalink
Added handling and telemetry for failed captcha initialization. (#1932)
Browse files Browse the repository at this point in the history
  • Loading branch information
azaslonov authored Sep 7, 2022
1 parent 6c8538e commit 1afb427
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 66 deletions.
40 changes: 25 additions & 15 deletions src/components/users/change-password/ko/runtime/change-password.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as ko from "knockout";
import * as validation from "knockout.validation";
import template from "./change-password.html";
import { Component, RuntimeComponent, OnMounted, Param } from "@paperbits/common/ko/decorators";
import { EventManager } from "@paperbits/common/events";
import { Component, OnMounted, Param, RuntimeComponent } from "@paperbits/common/ko/decorators";
import { Logger } from "@paperbits/common/logging";
import { ChangePasswordRequest } from "../../../../../contracts/resetRequest";
import { BackendService } from "../../../../../services/backendService";
import { UsersService } from "../../../../../services";
import { CaptchaData } from "../../../../../models/captchaData";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { UsersService } from "../../../../../services";
import { BackendService } from "../../../../../services/backendService";
import { ErrorSources } from "../../../validation-summary/constants";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { ValidationMessages } from "../../../validationMessages";


@RuntimeComponent({
selector: "change-password-runtime"
Expand All @@ -24,15 +27,16 @@ export class ChangePassword {
public readonly isChangeConfirmed: ko.Observable<boolean>;
public readonly working: ko.Observable<boolean>;
public readonly captcha: ko.Observable<string>;

public setCaptchaValidation: (captchaValidator: ko.Observable<string>) => void;
public refreshCaptcha: () => Promise<void>;
public readonly captchaData: ko.Observable<CaptchaData>;

constructor(
private readonly usersService: UsersService,
private readonly eventManager: EventManager,
private readonly backendService: BackendService
private readonly backendService: BackendService,
private readonly logger: Logger
) {
this.password = ko.observable();
this.newPassword = ko.observable();
Expand All @@ -49,10 +53,10 @@ export class ChangePassword {
decorateInputElement: true
});

this.password.extend(<any>{ required: { message: `Password is required.` } }); // TODO: password requirements should come from Management API.
this.newPassword.extend(<any>{ required: { message: `New password is required.` }, minLength: 8 }); // TODO: password requirements should come from Management API.
this.passwordConfirmation.extend(<any>{ required: { message: `Password confirmation is required.` }, equal: { message: "Password confirmation field must be equal to new password.", params: this.newPassword } });
this.captcha.extend(<any>{ required: { message: `Captcha is required.` } });
this.password.extend(<any>{ required: { message: ValidationMessages.passwordRequired } }); // TODO: password requirements should come from Management API.
this.newPassword.extend(<any>{ required: { message: ValidationMessages.newPasswordRequired }, minLength: 8 }); // TODO: password requirements should come from Management API.
this.passwordConfirmation.extend(<any>{ equal: { message: ValidationMessages.passwordConfirmationMustMatch, params: this.newPassword } });
this.captcha.extend(<any>{ required: { message: ValidationMessages.captchaRequired } });
}

@Param()
Expand All @@ -70,7 +74,7 @@ export class ChangePassword {
return;
}
}

public onCaptchaCreated(captchaValidate: (captchaValidator: ko.Observable<string>) => void, refreshCaptcha: () => Promise<void>) {
this.setCaptchaValidation = captchaValidate;
this.refreshCaptcha = refreshCaptcha;
Expand All @@ -80,14 +84,20 @@ export class ChangePassword {
* Sends user change password request to Management API.
*/
public async changePassword(): Promise<void> {
const isCaptcha = this.requireHipCaptcha();
const captchaIsRequired = this.requireHipCaptcha();
const validationGroup = {
password: this.password,
newPassword: this.newPassword,
passwordConfirmation: this.passwordConfirmation
};

if (isCaptcha) {
if (captchaIsRequired) {
if (!this.setCaptchaValidation) {
this.logger.trackEvent("CaptchaValidation", { message: "Captcha failed to initialize." });
dispatchErrors(this.eventManager, ErrorSources.resetpassword, [ValidationMessages.captchaNotInitialized]);
return;
}

validationGroup["captcha"] = this.captcha;
this.setCaptchaValidation(this.captcha);
}
Expand Down Expand Up @@ -117,7 +127,7 @@ export class ChangePassword {
this.working(true);
dispatchErrors(this.eventManager, ErrorSources.changepassword, []);

if (isCaptcha) {
if (captchaIsRequired) {
const captchaRequestData = this.captchaData();
const resetRequest: ChangePasswordRequest = {
challenge: captchaRequestData.challenge,
Expand All @@ -134,7 +144,7 @@ export class ChangePassword {
}
this.isChangeConfirmed(true);
} catch (error) {
if (isCaptcha) {
if (captchaIsRequired) {
await this.refreshCaptcha();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import * as ko from "knockout";
import * as validation from "knockout.validation";
import template from "./confirm-password.html";
import { EventManager } from "@paperbits/common/events";
import { Component, RuntimeComponent, OnMounted } from "@paperbits/common/ko/decorators";
import { Component, OnMounted, RuntimeComponent } from "@paperbits/common/ko/decorators";
import { UsersService } from "../../../../../services";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { ErrorSources } from "../../../validation-summary/constants";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { ValidationMessages } from "../../../validationMessages";

@RuntimeComponent({
selector: "confirm-password"
Expand Down Expand Up @@ -36,8 +37,8 @@ export class ConfirmPassword {
decorateInputElement: true
});

this.password.extend(<any>{ required: { message: `Password is required.` }, minLength: 8 }); // TODO: password requirements should come from Management API.
this.passwordConfirmation.extend(<any>{ required: { message: `Password confirmation is required.` }, equal: { message: "Password confirmation field must be equal to password.", params: this.password } });
this.password.extend(<any>{ required: { message: ValidationMessages.passwordRequired }, minLength: 8 }); // TODO: password requirements should come from Management API.
this.passwordConfirmation.extend(<any>{ equal: { message: ValidationMessages.passwordConfirmationMustMatch, params: this.password } });
}

/**
Expand Down
9 changes: 5 additions & 4 deletions src/components/users/profile/ko/runtime/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { EventManager } from "@paperbits/common/events/eventManager";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { ErrorSources } from "../../../validation-summary/constants";
import { BackendService } from "../../../../../services/backendService";
import { ValidationMessages } from "../../../validationMessages";

@RuntimeComponent({
selector: "profile-runtime"
Expand Down Expand Up @@ -57,8 +58,8 @@ export class Profile {
decorateInputElement: true
});

this.firstName.extend(<any>{ required: { message: `First name is required.` } });
this.lastName.extend(<any>{ required: { message: `Last name is required.` } });
this.firstName.extend(<any>{ required: { message: ValidationMessages.firstNameRequired } });
this.lastName.extend(<any>{ required: { message: ValidationMessages.lastNameRequired } });
}

@OnMounted()
Expand All @@ -77,7 +78,7 @@ export class Profile {
const isDelegationEnabled = await this.tenantService.isDelegationEnabled();
if (isDelegationEnabled) {
const delegationParam = {};
delegationParam[DelegationParameters.UserId] = Utils.getResourceName("users", this.user().id);
delegationParam[DelegationParameters.UserId] = Utils.getResourceName("users", this.user().id);
const delegationUrl = await this.backendService.getDelegationString(action, delegationParam);
if (delegationUrl) {
location.assign(delegationUrl);
Expand Down Expand Up @@ -124,7 +125,7 @@ export class Profile {
if (!this.isEdit()) {
return;
}

this.working(true);
dispatchErrors(this.eventManager, ErrorSources.changeProfile, []);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<!-- ko if: !isResetRequested() -->
<form data-bind="submit: resetSubmit">
<fieldset data-bind="attr: { disabled: working}">
<fieldset data-bind="attr: { disabled: working }">
<div class="form-group">
<label for="email">Email *</label>
<input aria-required="true" autofocus="autofocus" class="form-control" id="email" name="email"
Expand Down
43 changes: 26 additions & 17 deletions src/components/users/reset-password/ko/runtime/reset-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import * as ko from "knockout";
import * as validation from "knockout.validation";
import template from "./reset-password.html";
import { EventManager } from "@paperbits/common/events";
import { Component, RuntimeComponent, OnMounted, Param } from "@paperbits/common/ko/decorators";
import { UsersService } from "../../../../../services";
import { Component, OnMounted, Param, RuntimeComponent } from "@paperbits/common/ko/decorators";
import { Logger } from "@paperbits/common/logging";
import { ResetRequest } from "../../../../../contracts/resetRequest";
import { BackendService } from "../../../../../services/backendService";
import { CaptchaData } from "../../../../../models/captchaData";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { UsersService } from "../../../../../services";
import { BackendService } from "../../../../../services/backendService";
import { ErrorSources } from "../../../validation-summary/constants";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { ValidationMessages } from "../../../validationMessages";


@RuntimeComponent({
selector: "reset-password-runtime"
Expand All @@ -22,16 +25,16 @@ export class ResetPassword {
public readonly isResetRequested: ko.Observable<boolean>;
public readonly working: ko.Observable<boolean>;
public readonly captcha: ko.Observable<string>;

public readonly captchaData: ko.Observable<CaptchaData>;
public setCaptchaValidation: (captchaValidator: ko.Observable<string>) => void;
public refreshCaptcha: () => Promise<void>;
public readonly captchaData: ko.Observable<CaptchaData>;


constructor(
private readonly usersService: UsersService,
private readonly eventManager: EventManager,
private readonly backendService: BackendService) {
private readonly backendService: BackendService,
private readonly logger: Logger
) {
this.email = ko.observable();
this.isResetRequested = ko.observable(false);
this.working = ko.observable(false);
Expand All @@ -45,8 +48,8 @@ export class ResetPassword {
decorateInputElement: true
});

this.email.extend(<any>{ required: { message: `Email is required.` }, email: true });
this.captcha.extend(<any>{ required: { message: `Captcha is required.` } });
this.email.extend(<any>{ required: { message: ValidationMessages.emailRequired }, email: true });
this.captcha.extend(<any>{ required: { message: ValidationMessages.captchaRequired } });
}

@Param()
Expand All @@ -64,7 +67,7 @@ export class ResetPassword {
return;
}
}

public onCaptchaCreated(captchaValidate: (captchaValidator: ko.Observable<string>) => void, refreshCaptcha: () => Promise<void>) {
this.setCaptchaValidation = captchaValidate;
this.refreshCaptcha = refreshCaptcha;
Expand All @@ -74,10 +77,16 @@ export class ResetPassword {
* Sends user reset password request to Management API.
*/
public async resetSubmit(): Promise<void> {
const isCaptcha = this.requireHipCaptcha();
const captchaIsRequired = this.requireHipCaptcha();
const validationGroup = { email: this.email };

if (isCaptcha) {
if (captchaIsRequired) {
if (!this.setCaptchaValidation) {
this.logger.trackEvent("CaptchaValidation", { message: "Captcha failed to initialize." });
dispatchErrors(this.eventManager, ErrorSources.resetpassword, [ValidationMessages.captchaNotInitialized]);
return;
}

validationGroup["captcha"] = this.captcha;
this.setCaptchaValidation(this.captcha);
}
Expand All @@ -94,11 +103,11 @@ export class ResetPassword {

try {
this.working(true);
if (isCaptcha) {

if (captchaIsRequired) {
const captchaRequestData = this.captchaData();
const resetRequest: ResetRequest = {
challenge: captchaRequestData.challenge,
challenge: captchaRequestData.challenge,
solution: captchaRequestData.solution?.solution,
flowId: captchaRequestData.solution?.flowId,
token: captchaRequestData.solution?.token,
Expand All @@ -115,7 +124,7 @@ export class ResetPassword {
dispatchErrors(this.eventManager, ErrorSources.resetpassword, []);
}
catch (error) {
if (isCaptcha) {
if (captchaIsRequired) {
await this.refreshCaptcha();
}

Expand Down
7 changes: 4 additions & 3 deletions src/components/users/signin/ko/runtime/signin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UnauthorizedError } from "../../../../../errors/unauthorizedError";
import { UsersService } from "../../../../../services";
import { dispatchErrors } from "../../../validation-summary/utils";
import { ErrorSources } from "../../../validation-summary/constants";
import { ValidationMessages } from "../../../validationMessages";

@RuntimeComponent({
selector: "signin-runtime"
Expand Down Expand Up @@ -52,9 +53,9 @@ export class Signin {
decorateInputElement: true
});

this.username.extend(<any>{ required: { message: `Email is required.` }, email: true });
this.password.extend(<any>{ required: { message: `Password is required.` } });
this.consented.extend(<any>{ equal: { params: true, message: "You must agree to the terms of use." } });
this.username.extend(<any>{ required: { message: ValidationMessages.firstNameRequired }, email: true });
this.password.extend(<any>{ required: { message: ValidationMessages.passwordRequired } });
this.consented.extend(<any>{ equal: { params: true, message: ValidationMessages.consentRequired } });
}

@Param()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { RouteHelper } from "../../../../../routing/routeHelper";
import { UsersService } from "../../../../../services";
import { dispatchErrors, parseAndDispatchError } from "../../../validation-summary/utils";
import { ErrorSources } from "../../../validation-summary/constants";
import { ValidationMessages } from "../../../validationMessages";


@RuntimeComponent({
Expand Down Expand Up @@ -47,10 +48,10 @@ export class SignupSocial {
decorateInputElement: true
});

this.email.extend(<any>{ required: { message: `Email is required.` }, email: true });
this.firstName.extend(<any>{ required: { message: `First name is required.` } });
this.lastName.extend(<any>{ required: { message: `Last name is required.` } });
this.consented.extend(<any>{ equal: { params: true, message: "You must agree to the terms of use." } });
this.email.extend(<any>{ required: { message: ValidationMessages.emailRequired }, email: true });
this.firstName.extend(<any>{ required: { message: ValidationMessages.firstNameRequired } });
this.lastName.extend(<any>{ required: { message: ValidationMessages.lastNameRequired } });
this.consented.extend(<any>{ equal: { params: true, message: ValidationMessages.consentRequired } });
}

@Param()
Expand Down
Loading

0 comments on commit 1afb427

Please sign in to comment.