From d97e5d7e0a5079379d757bddd2672178351a3421 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Mon, 22 Jan 2024 21:34:14 +0000 Subject: [PATCH] AUT-2231: Fixed Interventions Change MFA Method Journeys --- ...ck-your-email-security-codes-controller.ts | 7 +- ...ur-email-security-codes-controller.test.ts | 81 ++++++++++++++++--- ...r-email-security-codes-integration.test.ts | 29 ++++++- .../check-your-email-controller.ts | 7 +- .../tests/check-your-email-controller.test.ts | 2 + .../common/state-machine/state-machine.ts | 6 ++ .../verify-code/verify-code-controller.ts | 29 ++++++- .../enter-mfa/enter-mfa-controller.ts | 27 ++++--- .../tests/enter-mfa-controller.test.ts | 4 + .../reset-password-2fa-sms-controller.ts | 7 +- .../reset-password-check-email-controller.ts | 7 +- 11 files changed, 175 insertions(+), 31 deletions(-) diff --git a/src/components/account-recovery/check-your-email-security-codes/check-your-email-security-codes-controller.ts b/src/components/account-recovery/check-your-email-security-codes/check-your-email-security-codes-controller.ts index eb89cf3ec..89b0a6a66 100644 --- a/src/components/account-recovery/check-your-email-security-codes/check-your-email-security-codes-controller.ts +++ b/src/components/account-recovery/check-your-email-security-codes/check-your-email-security-codes-controller.ts @@ -9,6 +9,8 @@ import { codeService } from "../../common/verify-code/verify-code-service"; import { verifyCodePost } from "../../common/verify-code/verify-code-controller"; import { ExpressRouteFunc } from "../../../types"; import { ERROR_CODES } from "../../common/constants"; +import { AccountInterventionsInterface } from "../../account-intervention/types"; +import { accountInterventionService } from "../../account-intervention/account-intervention-service"; const TEMPLATE_NAME = "account-recovery/check-your-email-security-codes/index.njk"; @@ -30,9 +32,10 @@ export function checkYourEmailSecurityCodesGet( } export const checkYourEmailSecurityCodesPost = ( - service: VerifyCodeInterface = codeService() + service: VerifyCodeInterface = codeService(), + accountInterventionsService: AccountInterventionsInterface = accountInterventionService() ): ExpressRouteFunc => { - return verifyCodePost(service, { + return verifyCodePost(service, accountInterventionsService, { notificationType: NOTIFICATION_TYPE.VERIFY_CHANGE_HOW_GET_SECURITY_CODES, template: TEMPLATE_NAME, validationKey: diff --git a/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-controller.test.ts b/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-controller.test.ts index 5809c2179..5a2627841 100644 --- a/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-controller.test.ts +++ b/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-controller.test.ts @@ -17,6 +17,7 @@ import { RequestOutput, ResponseOutput, } from "mock-req-res"; +import { AccountInterventionsInterface } from "../../../account-intervention/types"; describe("check your email change security codes controller", () => { let req: RequestOutput; @@ -48,22 +49,70 @@ describe("check your email change security codes controller", () => { }); describe("checkYourEmailChangeSecurityCodesPost", () => { - it("should redirect to /get-security-codes when valid code entered", async () => { - const fakeService: VerifyCodeInterface = { + it("should redirect to /get-security-codes and not call AIS when valid code entered and account interventions is turned on", async () => { + const fakeVerifyCodeService: VerifyCodeInterface = { verifyCode: sinon.fake.returns({ success: true, }), } as unknown as VerifyCodeInterface; + const fakeAccountInterventionsService: AccountInterventionsInterface = { + accountInterventionStatus: sinon.fake.returns({ + data: { + email: "test@test.com", + passwordResetRequired: false, + blocked: false, + temporarilySuspended: false, + }, + }), + } as unknown as AccountInterventionsInterface; + req.body.code = "123456"; req.session.id = "123456-djjad"; + req.session.user.email = "test@test.com"; - await checkYourEmailSecurityCodesPost(fakeService)( - req as Request, - res as Response - ); + await checkYourEmailSecurityCodesPost( + fakeVerifyCodeService, + fakeAccountInterventionsService + )(req as Request, res as Response); - expect(fakeService.verifyCode).to.have.been.calledOnce; + expect(fakeAccountInterventionsService.accountInterventionStatus).to.not + .have.been.calledOnce; + expect(fakeVerifyCodeService.verifyCode).to.have.been.calledOnce; + expect(res.redirect).to.have.calledWith(PATH_NAMES.GET_SECURITY_CODES); + }); + + it("should redirect to /get-security-codes when valid code entered and there are no interventions in place and account interventions is turned on", async () => { + process.env.SUPPORT_ACCOUNT_INTERVENTIONS = "1"; + const fakeVerifyCodeService: VerifyCodeInterface = { + verifyCode: sinon.fake.returns({ + success: true, + }), + } as unknown as VerifyCodeInterface; + + const fakeAccountInterventionsService: AccountInterventionsInterface = { + accountInterventionStatus: sinon.fake.returns({ + data: { + email: "test@test.com", + passwordResetRequired: false, + blocked: false, + temporarilySuspended: false, + }, + }), + } as unknown as AccountInterventionsInterface; + + req.body.code = "123456"; + req.session.id = "123456-djjad"; + req.session.user.email = "test@test.com"; + + await checkYourEmailSecurityCodesPost( + fakeVerifyCodeService, + fakeAccountInterventionsService + )(req as Request, res as Response); + + expect(fakeAccountInterventionsService.accountInterventionStatus).to.have + .been.calledOnce; + expect(fakeVerifyCodeService.verifyCode).to.have.been.calledOnce; expect(res.redirect).to.have.calledWith(PATH_NAMES.GET_SECURITY_CODES); }); @@ -77,11 +126,21 @@ describe("check your email change security codes controller", () => { req.body.code = "678988"; req.session.id = "123456-djjad"; + req.session.user.email = "test@test.com"; - await checkYourEmailSecurityCodesPost(fakeService)( - req as Request, - res as Response - ); + const fakeAccountInterventionsService: AccountInterventionsInterface = { + accountInterventionStatus: sinon.fake.returns({ + email: "test@test.com", + passwordResetRequired: false, + blocked: false, + temporarilySuspended: false, + }), + } as unknown as AccountInterventionsInterface; + + await checkYourEmailSecurityCodesPost( + fakeService, + fakeAccountInterventionsService + )(req as Request, res as Response); expect(fakeService.verifyCode).to.have.been.calledOnce; expect(res.render).to.have.been.calledWith( diff --git a/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-integration.test.ts b/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-integration.test.ts index 81c210749..8b4629069 100644 --- a/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-integration.test.ts +++ b/src/components/account-recovery/check-your-email-security-codes/tests/check-your-email-security-codes-integration.test.ts @@ -14,6 +14,10 @@ import { ERROR_CODES, SecurityCodeErrorType } from "../../../common/constants"; import { SendNotificationServiceInterface } from "../../../common/send-notification/types"; import { DefaultApiResponse } from "../../../../types"; import { createApiResponse } from "../../../../utils/http"; +import { + AccountInterventionsInterface, + AccountInterventionStatus, +} from "../../../account-intervention/types"; describe("Integration:: check your email security codes", () => { let token: string | string[]; @@ -27,6 +31,7 @@ describe("Integration:: check your email security codes", () => { decache("../../../common/send-notification/send-notification-service"); const sessionMiddleware = require("../../../../middleware/session-middleware"); const sendNotificationService = require("../../../common/send-notification/send-notification-service"); + const accountInterventionService = require("../../../account-intervention/account-intervention-service"); sinon .stub(sessionMiddleware, "validateSessionMiddleware") .callsFake(function (req: any, res: any, next: any): void { @@ -48,6 +53,27 @@ describe("Integration:: check your email security codes", () => { next(); }); + sinon + .stub(accountInterventionService, "accountInterventionService") + .callsFake((): AccountInterventionsInterface => { + async function accountInterventionStatus() { + const fakeAxiosResponse: AxiosResponse = { + data: { + passwordResetRequired: false, + blocked: false, + temporarilySuspended: false, + }, + status: HTTP_STATUS_CODES.OK, + } as AxiosResponse; + + return createApiResponse( + fakeAxiosResponse + ); + } + + return { accountInterventionStatus }; + }); + sinon .stub(sendNotificationService, "sendNotificationService") .callsFake((): SendNotificationServiceInterface => { @@ -173,7 +199,8 @@ describe("Integration:: check your email security codes", () => { .expect(400, done); }); - it("should redirect to /create-password when valid code entered", (done) => { + it("should redirect to /get-security-codes when valid code entered", (done) => { + process.env.SUPPORT_ACCOUNT_RECOVERY = "1"; nock(baseApi) .post(API_ENDPOINTS.VERIFY_CODE) .once() diff --git a/src/components/check-your-email/check-your-email-controller.ts b/src/components/check-your-email/check-your-email-controller.ts index afb018f11..9e05c0439 100644 --- a/src/components/check-your-email/check-your-email-controller.ts +++ b/src/components/check-your-email/check-your-email-controller.ts @@ -5,6 +5,8 @@ import { codeService } from "../common/verify-code/verify-code-service"; import { verifyCodePost } from "../common/verify-code/verify-code-controller"; import { ExpressRouteFunc } from "../../types"; import { ERROR_CODES } from "../common/constants"; +import { AccountInterventionsInterface } from "../account-intervention/types"; +import { accountInterventionService } from "../account-intervention/account-intervention-service"; const TEMPLATE_NAME = "check-your-email/index.njk"; @@ -15,9 +17,10 @@ export function checkYourEmailGet(req: Request, res: Response): void { } export const checkYourEmailPost = ( - service: VerifyCodeInterface = codeService() + service: VerifyCodeInterface = codeService(), + accountInterventionsService: AccountInterventionsInterface = accountInterventionService() ): ExpressRouteFunc => { - return verifyCodePost(service, { + return verifyCodePost(service, accountInterventionsService, { notificationType: NOTIFICATION_TYPE.VERIFY_EMAIL, template: TEMPLATE_NAME, validationKey: "pages.checkYourEmail.code.validationError.invalidCode", diff --git a/src/components/check-your-email/tests/check-your-email-controller.test.ts b/src/components/check-your-email/tests/check-your-email-controller.test.ts index 613ea7336..b1728d924 100644 --- a/src/components/check-your-email/tests/check-your-email-controller.test.ts +++ b/src/components/check-your-email/tests/check-your-email-controller.test.ts @@ -55,6 +55,7 @@ describe("check your email controller", () => { req.body.code = "123456"; req.session.id = "123456-djjad"; + req.session.user.email = "test@test.com"; await checkYourEmailPost(fakeService)(req as Request, res as Response); @@ -74,6 +75,7 @@ describe("check your email controller", () => { req.body.code = "678988"; req.session.id = "123456-djjad"; + req.session.user.email = "test@test.com"; await checkYourEmailPost(fakeService)(req as Request, res as Response); diff --git a/src/components/common/state-machine/state-machine.ts b/src/components/common/state-machine/state-machine.ts index ebe32498c..973e13fec 100644 --- a/src/components/common/state-machine/state-machine.ts +++ b/src/components/common/state-machine/state-machine.ts @@ -688,6 +688,12 @@ const authStateMachine = createMachine( [USER_JOURNEY_EVENTS.EMAIL_SECURITY_CODES_CODE_VERIFIED]: [ PATH_NAMES.GET_SECURITY_CODES, ], + [USER_JOURNEY_EVENTS.TEMPORARILY_BLOCKED_INTERVENTION]: [ + PATH_NAMES.UNAVAILABLE_TEMPORARY, + ], + [USER_JOURNEY_EVENTS.PERMANENTLY_BLOCKED_INTERVENTION]: [ + PATH_NAMES.UNAVAILABLE_PERMANENT, + ], }, meta: { optionalPaths: [ diff --git a/src/components/common/verify-code/verify-code-controller.ts b/src/components/common/verify-code/verify-code-controller.ts index 63c32a68f..f1f3f52cb 100644 --- a/src/components/common/verify-code/verify-code-controller.ts +++ b/src/components/common/verify-code/verify-code-controller.ts @@ -9,7 +9,11 @@ import { VerifyCodeInterface } from "./types"; import { ExpressRouteFunc } from "../../../types"; import { USER_JOURNEY_EVENTS } from "../state-machine/state-machine"; import { JOURNEY_TYPE, NOTIFICATION_TYPE } from "../../../app.constants"; -import { support2FABeforePasswordReset } from "../../../config"; +import { + support2FABeforePasswordReset, + supportAccountInterventions, +} from "../../../config"; +import { AccountInterventionsInterface } from "../../account-intervention/types"; interface Config { notificationType: NOTIFICATION_TYPE; @@ -22,9 +26,11 @@ interface Config { export function verifyCodePost( service: VerifyCodeInterface, + accountInterventionsService: AccountInterventionsInterface, options: Config ): ExpressRouteFunc { return async function (req: Request, res: Response) { + const email = req.session.user.email.toLowerCase(); const code = req.body["code"]; const { sessionId, clientSessionId, persistentSessionId } = res.locals; @@ -62,6 +68,7 @@ export function verifyCodePost( } let nextEvent; + let accountInterventionsResponse; switch (options.notificationType) { case NOTIFICATION_TYPE.VERIFY_EMAIL: @@ -79,6 +86,26 @@ export function verifyCodePost( default: throw new Error("Unknown notification type"); } + if (supportAccountInterventions()) { + if ( + nextEvent === USER_JOURNEY_EVENTS.EMAIL_SECURITY_CODES_CODE_VERIFIED + ) { + accountInterventionsResponse = + await accountInterventionsService.accountInterventionStatus( + sessionId, + email, + req.ip, + clientSessionId, + persistentSessionId + ); + if (accountInterventionsResponse.data.blocked) { + nextEvent = USER_JOURNEY_EVENTS.PERMANENTLY_BLOCKED_INTERVENTION; + } + if (accountInterventionsResponse.data.temporarilySuspended) { + nextEvent = USER_JOURNEY_EVENTS.TEMPORARILY_BLOCKED_INTERVENTION; + } + } + } res.redirect( getNextPathAndUpdateJourney( diff --git a/src/components/enter-mfa/enter-mfa-controller.ts b/src/components/enter-mfa/enter-mfa-controller.ts index 023ba17b4..1c7ced052 100644 --- a/src/components/enter-mfa/enter-mfa-controller.ts +++ b/src/components/enter-mfa/enter-mfa-controller.ts @@ -14,6 +14,8 @@ import { AccountRecoveryInterface } from "../common/account-recovery/types"; import { accountRecoveryService } from "../common/account-recovery/account-recovery-service"; import { BadRequestError } from "../../utils/error"; import { getJourneyTypeFromUserSession } from "../common/journey/journey"; +import { AccountInterventionsInterface } from "../account-intervention/types"; +import { accountInterventionService } from "../account-intervention/account-intervention-service"; export const ENTER_MFA_DEFAULT_TEMPLATE_NAME = "enter-mfa/index.njk"; export const UPLIFT_REQUIRED_SMS_TEMPLATE_NAME = @@ -72,7 +74,8 @@ export function enterMfaGet( } export const enterMfaPost = ( - service: VerifyCodeInterface = codeService() + service: VerifyCodeInterface = codeService(), + accountInterventionsService: AccountInterventionsInterface = accountInterventionService() ): ExpressRouteFunc => { return async function (req: Request, res: Response) { const { isUpliftRequired } = req.session.user; @@ -81,15 +84,19 @@ export const enterMfaPost = ( ? UPLIFT_REQUIRED_SMS_TEMPLATE_NAME : ENTER_MFA_DEFAULT_TEMPLATE_NAME; - const verifyCodeRequest = verifyCodePost(service, { - notificationType: NOTIFICATION_TYPE.MFA_SMS, - template: template, - validationKey: "pages.enterMfa.code.validationError.invalidCode", - validationErrorCode: ERROR_CODES.INVALID_MFA_CODE, - journeyType: getJourneyTypeFromUserSession(req.session.user, { - includeReauthentication: true, - }), - }); + const verifyCodeRequest = verifyCodePost( + service, + accountInterventionsService, + { + notificationType: NOTIFICATION_TYPE.MFA_SMS, + template: template, + validationKey: "pages.enterMfa.code.validationError.invalidCode", + validationErrorCode: ERROR_CODES.INVALID_MFA_CODE, + journeyType: getJourneyTypeFromUserSession(req.session.user, { + includeReauthentication: true, + }), + } + ); return verifyCodeRequest(req, res); }; diff --git a/src/components/enter-mfa/tests/enter-mfa-controller.test.ts b/src/components/enter-mfa/tests/enter-mfa-controller.test.ts index a14cf4d33..32e6449ea 100644 --- a/src/components/enter-mfa/tests/enter-mfa-controller.test.ts +++ b/src/components/enter-mfa/tests/enter-mfa-controller.test.ts @@ -150,6 +150,7 @@ describe("enter mfa controller", () => { req.body.code = "123456"; res.locals.sessionId = "123456-djjad"; req.session.user.reauthenticate = "test_data"; + req.session.user.email = "test@test.com"; await enterMfaPost(fakeService)(req as Request, res as Response); @@ -181,6 +182,7 @@ describe("enter mfa controller", () => { req.body.code = "123456"; res.locals.sessionId = "123456-djjad"; + req.session.user.email = "test@test.com"; await enterMfaPost(fakeService)(req as Request, res as Response); @@ -201,6 +203,7 @@ describe("enter mfa controller", () => { req.t = sinon.fake.returns("translated string"); req.body.code = "678988"; res.locals.sessionId = "123456-djjad"; + req.session.user.email = "test@test.com"; await enterMfaPost(fakeService)(req as Request, res as Response); @@ -222,6 +225,7 @@ describe("enter mfa controller", () => { req.t = sinon.fake.returns("translated string"); req.body.code = "678988"; res.locals.sessionId = "123456-djjad"; + req.session.user.email = "test@test.com"; await enterMfaPost(fakeService)(req as Request, res as Response); diff --git a/src/components/reset-password-2fa-sms/reset-password-2fa-sms-controller.ts b/src/components/reset-password-2fa-sms/reset-password-2fa-sms-controller.ts index 3ff1d0b3d..5cc924660 100644 --- a/src/components/reset-password-2fa-sms/reset-password-2fa-sms-controller.ts +++ b/src/components/reset-password-2fa-sms/reset-password-2fa-sms-controller.ts @@ -9,6 +9,8 @@ import { JOURNEY_TYPE, NOTIFICATION_TYPE } from "../../app.constants"; import { verifyCodePost } from "../common/verify-code/verify-code-controller"; import { VerifyCodeInterface } from "../common/verify-code/types"; import { codeService } from "../common/verify-code/verify-code-service"; +import { AccountInterventionsInterface } from "../account-intervention/types"; +import { accountInterventionService } from "../account-intervention/account-intervention-service"; const TEMPLATE_NAME = "reset-password-2fa-sms/index.njk"; export function resetPassword2FASmsGet( @@ -42,9 +44,10 @@ export function resetPassword2FASmsGet( }; } export function resetPassword2FASmsPost( - service: VerifyCodeInterface = codeService() + service: VerifyCodeInterface = codeService(), + accountInterventionsService: AccountInterventionsInterface = accountInterventionService() ): ExpressRouteFunc { - return verifyCodePost(service, { + return verifyCodePost(service, accountInterventionsService, { notificationType: NOTIFICATION_TYPE.MFA_SMS, template: TEMPLATE_NAME, validationKey: "pages.passwordResetMfaSms.code.validationError.invalidCode", diff --git a/src/components/reset-password-check-email/reset-password-check-email-controller.ts b/src/components/reset-password-check-email/reset-password-check-email-controller.ts index 1c2a8f767..77cc3cfa1 100644 --- a/src/components/reset-password-check-email/reset-password-check-email-controller.ts +++ b/src/components/reset-password-check-email/reset-password-check-email-controller.ts @@ -9,6 +9,8 @@ import { codeService } from "../common/verify-code/verify-code-service"; import { verifyCodePost } from "../common/verify-code/verify-code-controller"; import { NOTIFICATION_TYPE } from "../../app.constants"; import { support2FABeforePasswordReset } from "../../config"; +import { AccountInterventionsInterface } from "../account-intervention/types"; +import { accountInterventionService } from "../account-intervention/account-intervention-service"; const TEMPLATE_NAME = "reset-password-check-email/index.njk"; @@ -88,9 +90,10 @@ export function resetPasswordCheckEmailGet( } export function resetPasswordCheckEmailPost( - service: VerifyCodeInterface = codeService() + service: VerifyCodeInterface = codeService(), + accountInterventionsService: AccountInterventionsInterface = accountInterventionService() ): ExpressRouteFunc { - return verifyCodePost(service, { + return verifyCodePost(service, accountInterventionsService, { notificationType: NOTIFICATION_TYPE.RESET_PASSWORD_WITH_CODE, template: TEMPLATE_NAME, validationKey: