Skip to content

Commit

Permalink
Merge pull request #1631 from govuk-one-login/origin/AUT-2164/Create_…
Browse files Browse the repository at this point in the history
…code_for_a_new_endpoint_on_the_Auth_Internal_API

AUT-2164: Add service for checking fraud emails and feature switch
  • Loading branch information
LazarAlexandru-Constantin authored May 21, 2024
2 parents d3e5c85 + e912dd4 commit b8b7608
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 15 deletions.
1 change: 1 addition & 0 deletions ci/terraform/authdev1.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ gtm_id = ""
support_account_recovery = "1"
support_authorize_controller = "1"
support_2fa_b4_password_reset = "1"
support_check_email_fraud = "1"
language_toggle_enabled = "1"

frontend_task_definition_cpu = 512
Expand Down
1 change: 1 addition & 0 deletions ci/terraform/authdev2.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ gtm_id = ""
support_account_recovery = "1"
support_authorize_controller = "1"
support_2fa_b4_password_reset = "1"
support_check_email_fraud = "1"
language_toggle_enabled = "1"

frontend_task_definition_cpu = 512
Expand Down
1 change: 1 addition & 0 deletions ci/terraform/build.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ support_account_interventions = "1"
support_reauthentication = "1"
support_2fa_b4_password_reset = "1"
support_2hr_lockout = "1"
support_check_email_fraud = "1"
password_reset_code_entered_wrong_blocked_minutes = "1"
account_recovery_code_entered_wrong_blocked_minutes = "1"
code_request_blocked_minutes = "1"
Expand Down
4 changes: 4 additions & 0 deletions ci/terraform/ecs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ locals {
name = "SUPPORT_2HR_LOCKOUT"
value = var.support_2hr_lockout
},
{
name = "SUPPORT_CHECK_EMAIL_FRAUD"
value = var.support_check_email_fraud
},
{
name = "LANGUAGE_TOGGLE_ENABLED"
value = var.language_toggle_enabled
Expand Down
1 change: 1 addition & 0 deletions ci/terraform/sandpit.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ support_account_recovery = "1"
support_authorize_controller = "1"
support_account_interventions = "1"
support_2fa_b4_password_reset = "1"
support_check_email_fraud = "1"
language_toggle_enabled = "1"


Expand Down
1 change: 1 addition & 0 deletions ci/terraform/staging.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ support_account_interventions = "1"
support_authorize_controller = "1"
support_2fa_b4_password_reset = "1"
support_2hr_lockout = "1"
support_check_email_fraud = "1"
code_request_blocked_minutes = "120"
account_recovery_code_entered_wrong_blocked_minutes = "120"
code_entered_wrong_blocked_minutes = "120"
Expand Down
6 changes: 6 additions & 0 deletions ci/terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ variable "support_reauthentication" {
default = "0"
}

variable "support_check_email_fraud" {
description = "When true enables Fraudulent email checking via Experian lockout"
type = string
default = "0"
}

variable "email_entered_wrong_blocked_minutes" {
description = "The duration, in minutes, for which a user is blocked after entering the wrong email multiple times during reauthentication"
default = "15"
Expand Down
1 change: 1 addition & 0 deletions scripts/_create_env_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class EnvFileSection(TypedDict):
"SUPPORT_2FA_B4_PASSWORD_RESET": 1,
"SUPPORT_REAUTHENTICATION": 1,
"SUPPORT_2HR_LOCKOUT": 1,
"SUPPORT_CHECK_EMAIL_FRAUD": 1,
},
},
{
Expand Down
1 change: 1 addition & 0 deletions src/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const API_ENDPOINTS = {
VERIFY_MFA_CODE: "/verify-mfa-code",
ACCOUNT_RECOVERY: "/account-recovery",
CHECK_REAUTH_USER: "/check-reauth-user",
CHECK_EMAIL_FRAUD_BLOCK: "/check-email-fraud-block",
};

export const ERROR_MESSAGES = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
createApiResponse,
getRequestConfig,
Http,
http,
} from "../../utils/http";
import { API_ENDPOINTS } from "../../app.constants";
import { ApiResponseResult } from "../../types";
import {
CheckEmailFraudBlockInterface,
CheckEmailFraudBlockResponse,
} from "./types";

export function checkEmailFraudBlockService(
axios: Http = http
): CheckEmailFraudBlockInterface {
const checkEmailFraudBlock = async function (
email: string,
sessionId: string,
sourceIp: string,
clientSessionId: string,
persistentSessionId: string
): Promise<ApiResponseResult<CheckEmailFraudBlockResponse>> {
const response = await axios.client.post<CheckEmailFraudBlockResponse>(
API_ENDPOINTS.CHECK_EMAIL_FRAUD_BLOCK,
{
email: email.toLowerCase(),
},
getRequestConfig({
sessionId: sessionId,
sourceIp: sourceIp,
clientSessionId: clientSessionId,
persistentSessionId: persistentSessionId,
})
);
return createApiResponse<CheckEmailFraudBlockResponse>(response);
};
return {
checkEmailFraudBlock,
};
}
16 changes: 16 additions & 0 deletions src/components/check-email-fraud-block/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ApiResponseResult, DefaultApiResponse } from "../../types";

export interface CheckEmailFraudBlockInterface {
checkEmailFraudBlock: (
email: string,
sessionId: string,
sourceIp: string,
clientSessionId: string,
persistentSessionId: string
) => Promise<ApiResponseResult<CheckEmailFraudBlockResponse>>;
}

export interface CheckEmailFraudBlockResponse extends DefaultApiResponse {
email: string;
isBlockedStatus: string;
}
18 changes: 17 additions & 1 deletion src/components/enter-email/enter-email-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { checkReauthUsersService } from "../check-reauth-users/check-reauth-user
import {
getEmailEnteredWrongBlockDurationInMinutes,
support2hrLockout,
supportCheckEmailFraud,
supportReauthentication,
} from "../../config";
import {
Expand All @@ -31,6 +32,9 @@ import {
timestampNMinutesFromNow,
timestampNSecondsFromNow,
} from "../../utils/lock-helper";
import { CheckEmailFraudBlockInterface } from "../check-email-fraud-block/types";
import { checkEmailFraudBlockService } from "../check-email-fraud-block/checkEmailFraudBlockService";
import { logger } from "../../utils/logger";

export const RE_ENTER_EMAIL_TEMPLATE =
"enter-email/index-re-enter-email-account.njk";
Expand Down Expand Up @@ -59,7 +63,8 @@ export function enterEmailCreateGet(req: Request, res: Response): void {

export function enterEmailPost(
service: EnterEmailServiceInterface = enterEmailService(),
checkReauthService: CheckReauthServiceInterface = checkReauthUsersService()
checkReauthService: CheckReauthServiceInterface = checkReauthUsersService(),
checkEmailFraudService: CheckEmailFraudBlockInterface = checkEmailFraudBlockService()
): ExpressRouteFunc {
return async function (req: Request, res: Response) {
const email = req.body.email;
Expand Down Expand Up @@ -126,6 +131,17 @@ export function enterEmailPost(
result.data.lockoutInformation.length > 0
)
setUpAuthAppLocks(req, result.data.lockoutInformation);
if (supportCheckEmailFraud()) {
const checkEmailFraudResponse =
await checkEmailFraudService.checkEmailFraudBlock(
email,
sessionId,
req.ip,
clientSessionId,
persistentSessionId
);
logger.info(`checkEmailFraudResponse: ${checkEmailFraudResponse.data}`);
}
req.session.user.enterEmailMfaType = result.data.mfaMethodType;
req.session.user.redactedPhoneNumber = result.data.phoneNumberLastThree;
const nextState = result.data.doesUserExist
Expand Down
47 changes: 34 additions & 13 deletions src/components/enter-email/tests/enter-email-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,27 @@ import { SendNotificationServiceInterface } from "../../common/send-notification
import { mockResponse, RequestOutput, ResponseOutput } from "mock-req-res";
import { CheckReauthServiceInterface } from "../../check-reauth-users/types";
import { createMockRequest } from "../../../../test/helpers/mock-request-helper";
import { CheckEmailFraudBlockInterface } from "../../check-email-fraud-block/types";

describe("enter email controller", () => {
let req: RequestOutput;
let res: ResponseOutput;
let clock: sinon.SinonFakeTimers;
const date = new Date(Date.UTC(2024, 1, 1));

const checkReauthSuccessfulFakeService: CheckReauthServiceInterface = {
checkReauthUsers: sinon.fake.returns({
success: true,
}),
} as unknown as CheckReauthServiceInterface;

const checkEmailFraudFakeSuccessfulService: CheckEmailFraudBlockInterface = {
checkEmailFraudBlock: sinon.fake.returns({
success: true,
data: { email: "[email protected]", isBlockedStatus: "Pending" },
}),
} as unknown as CheckEmailFraudBlockInterface;

beforeEach(() => {
res = mockResponse();
clock = sinon.useFakeTimers({
Expand Down Expand Up @@ -145,7 +159,11 @@ describe("enter email controller", () => {
req.body.email = "test.test.com";
res.locals.sessionId = "dsad.dds";

await enterEmailPost(fakeService)(req as Request, res as Response);
await enterEmailPost(
fakeService,
checkReauthSuccessfulFakeService,
checkEmailFraudFakeSuccessfulService
)(req as Request, res as Response);

expect(fakeService.userExists).to.have.been.calledOnce;
expect(res.redirect).to.have.calledWith(PATH_NAMES.ENTER_PASSWORD);
Expand All @@ -162,7 +180,11 @@ describe("enter email controller", () => {
req.body.email = "test.test.com";
res.locals.sessionId = "sadl990asdald";

await enterEmailPost(fakeService)(req as Request, res as Response);
await enterEmailPost(
fakeService,
checkReauthSuccessfulFakeService,
checkEmailFraudFakeSuccessfulService
)(req as Request, res as Response);

expect(res.redirect).to.have.calledWith(PATH_NAMES.ACCOUNT_NOT_FOUND);
expect(fakeService.userExists).to.have.been.calledOnce;
Expand Down Expand Up @@ -190,7 +212,11 @@ describe("enter email controller", () => {
req.body.email = "[email protected]";
res.locals.sessionId = "sadl990asdald";

await enterEmailPost(fakeService)(req as Request, res as Response);
await enterEmailPost(
fakeService,
checkReauthSuccessfulFakeService,
checkEmailFraudFakeSuccessfulService
)(req as Request, res as Response);

const expectedLockTime = new Date(
date.getTime() + lockTTlInSeconds * 1000
Expand Down Expand Up @@ -450,16 +476,11 @@ describe("enter email controller", () => {
}),
} as unknown as EnterEmailServiceInterface;

const successfulFakeService: CheckReauthServiceInterface = {
checkReauthUsers: sinon.fake.returns({
success: true,
}),
} as unknown as CheckReauthServiceInterface;

await enterEmailPost(fakeService, successfulFakeService)(
req as Request,
res as Response
);
await enterEmailPost(
fakeService,
checkReauthSuccessfulFakeService,
checkEmailFraudFakeSuccessfulService
)(req as Request, res as Response);

expect(res.redirect).to.have.calledWith(PATH_NAMES.ENTER_PASSWORD);
});
Expand Down
25 changes: 24 additions & 1 deletion src/components/enter-email/tests/enter-email-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import request from "supertest";
import { describe } from "mocha";
import { expect, sinon } from "../../../../test/utils/test-utils";
import nock = require("nock");
import * as cheerio from "cheerio";
import decache from "decache";
import {
Expand All @@ -13,6 +12,7 @@ import { CheckReauthServiceInterface } from "../../check-reauth-users/types";
import { AxiosResponse } from "axios";
import { createApiResponse } from "../../../utils/http";
import { DefaultApiResponse } from "../../../types";
import nock = require("nock");

describe("Integration::enter email", () => {
let token: string | string[];
Expand All @@ -21,6 +21,7 @@ describe("Integration::enter email", () => {
let baseApi: string;

before(async () => {
process.env.SUPPORT_CHECK_EMAIL_FRAUD = "1";
decache("../../../app");
decache("../../../middleware/session-middleware");
const sessionMiddleware = require("../../../middleware/session-middleware");
Expand Down Expand Up @@ -167,6 +168,13 @@ describe("Integration::enter email", () => {
email: "[email protected]",
doesUserExist: true,
});
nock(baseApi)
.post(API_ENDPOINTS.CHECK_EMAIL_FRAUD_BLOCK)
.once()
.reply(HTTP_STATUS_CODES.OK, {
email: "[email protected]",
isBlockedStatus: "Pending",
});

request(app)
.post(PATH_NAMES.ENTER_EMAIL_SIGN_IN)
Expand All @@ -185,6 +193,13 @@ describe("Integration::enter email", () => {
email: "[email protected]",
doesUserExist: false,
});
nock(baseApi)
.post(API_ENDPOINTS.CHECK_EMAIL_FRAUD_BLOCK)
.once()
.reply(HTTP_STATUS_CODES.OK, {
email: "[email protected]",
isBlockedStatus: "Pending",
});

request(app)
.post(PATH_NAMES.ENTER_EMAIL_SIGN_IN)
Expand Down Expand Up @@ -236,6 +251,14 @@ describe("Integration::enter email", () => {
doesUserExist: true,
});

nock(baseApi)
.post(API_ENDPOINTS.CHECK_EMAIL_FRAUD_BLOCK)
.once()
.reply(HTTP_STATUS_CODES.OK, {
email: "[email protected]",
isBlockedStatus: "Pending",
});

request(app)
.post(PATH_NAMES.ENTER_EMAIL_SIGN_IN)
.type("form")
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ export function supportReauthentication(): boolean {
return process.env.SUPPORT_REAUTHENTICATION === "1";
}

export function supportCheckEmailFraud(): boolean {
return process.env.SUPPORT_CHECK_EMAIL_FRAUD === "1";
}

export function getLanguageToggleEnabled(): boolean {
return process.env.LANGUAGE_TOGGLE_ENABLED === "1";
}
Expand Down

0 comments on commit b8b7608

Please sign in to comment.