Skip to content

Commit

Permalink
Merge pull request #1310 from govuk-one-login/AUT-2231/fix-interventi…
Browse files Browse the repository at this point in the history
…ons-with-change-security-codes

AUT-2231: Fixed Account Interventions/Change MFA Method Journeys
  • Loading branch information
dbes-gds authored Feb 7, 2024
2 parents 826e618 + d97e5d7 commit 79be009
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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: "[email protected]",
passwordResetRequired: false,
blocked: false,
temporarilySuspended: false,
},
}),
} as unknown as AccountInterventionsInterface;

req.body.code = "123456";
req.session.id = "123456-djjad";
req.session.user.email = "[email protected]";

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: "[email protected]",
passwordResetRequired: false,
blocked: false,
temporarilySuspended: false,
},
}),
} as unknown as AccountInterventionsInterface;

req.body.code = "123456";
req.session.id = "123456-djjad";
req.session.user.email = "[email protected]";

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);
});

Expand All @@ -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 = "[email protected]";

await checkYourEmailSecurityCodesPost(fakeService)(
req as Request,
res as Response
);
const fakeAccountInterventionsService: AccountInterventionsInterface = {
accountInterventionStatus: sinon.fake.returns({
email: "[email protected]",
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -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 {
Expand All @@ -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<AccountInterventionStatus>(
fakeAxiosResponse
);
}

return { accountInterventionStatus };
});

sinon
.stub(sendNotificationService, "sendNotificationService")
.callsFake((): SendNotificationServiceInterface => {
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe("check your email controller", () => {

req.body.code = "123456";
req.session.id = "123456-djjad";
req.session.user.email = "[email protected]";

await checkYourEmailPost(fakeService)(req as Request, res as Response);

Expand All @@ -74,6 +75,7 @@ describe("check your email controller", () => {

req.body.code = "678988";
req.session.id = "123456-djjad";
req.session.user.email = "[email protected]";

await checkYourEmailPost(fakeService)(req as Request, res as Response);

Expand Down
6 changes: 6 additions & 0 deletions src/components/common/state-machine/state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
29 changes: 28 additions & 1 deletion src/components/common/verify-code/verify-code-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -62,6 +68,7 @@ export function verifyCodePost(
}

let nextEvent;
let accountInterventionsResponse;

switch (options.notificationType) {
case NOTIFICATION_TYPE.VERIFY_EMAIL:
Expand All @@ -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(
Expand Down
27 changes: 17 additions & 10 deletions src/components/enter-mfa/enter-mfa-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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;
Expand All @@ -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);
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/enter-mfa/tests/enter-mfa-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "[email protected]";

await enterMfaPost(fakeService)(req as Request, res as Response);

Expand Down Expand Up @@ -181,6 +182,7 @@ describe("enter mfa controller", () => {

req.body.code = "123456";
res.locals.sessionId = "123456-djjad";
req.session.user.email = "[email protected]";

await enterMfaPost(fakeService)(req as Request, res as Response);

Expand All @@ -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 = "[email protected]";

await enterMfaPost(fakeService)(req as Request, res as Response);

Expand All @@ -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 = "[email protected]";

await enterMfaPost(fakeService)(req as Request, res as Response);

Expand Down
Loading

0 comments on commit 79be009

Please sign in to comment.