Skip to content

Commit f366137

Browse files
committed
AUT-4248: Send mfa method ID when requesting MFA
- This is so that the BE knows which number to send the OTP to
1 parent c83cc56 commit f366137

11 files changed

Lines changed: 200 additions & 55 deletions

src/components/common/mfa/mfa-service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function mfaService(axios: Http = http): MfaServiceInterface {
1919
isResendCodeRequest: boolean,
2020
userLanguage: string,
2121
req: Request,
22+
mfaMethodId: string,
2223
journeyType?: JOURNEY_TYPE
2324
): Promise<ApiResponseResult<DefaultApiResponse>> {
2425
const response = await axios.client.post<DefaultApiResponse>(
@@ -27,6 +28,7 @@ export function mfaService(axios: Http = http): MfaServiceInterface {
2728
email: emailAddress,
2829
isResendCodeRequest,
2930
journeyType,
31+
mfaMethodId,
3032
},
3133
getInternalRequestConfigWithSecurityHeaders(
3234
{

src/components/common/mfa/tests/mfa-service.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ describe("mfa service", () => {
4949
const userLanguage = "cy";
5050
const journeyType = JOURNEY_TYPE.SIGN_IN;
5151
const isResendCodeRequest = true;
52+
const mfaMethodId = "9b1deb4d-3b7d-4bad-9bdd-2b0d7a3a03d7";
5253

5354
const expectedApiCallDetails = {
5455
expectedPath: API_ENDPOINTS.MFA,
5556
expectedHeaders: {
5657
...expectedHeadersFromCommonVarsWithSecurityHeaders,
5758
"User-Language": userLanguage,
5859
},
59-
expectedBody: { email, isResendCodeRequest, journeyType },
60+
expectedBody: { email, isResendCodeRequest, journeyType, mfaMethodId },
6061
};
6162

6263
const result = await service.sendMfaCode(
@@ -67,6 +68,7 @@ describe("mfa service", () => {
6768
isResendCodeRequest,
6869
userLanguage,
6970
req,
71+
mfaMethodId,
7072
journeyType
7173
);
7274

src/components/common/mfa/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface MfaServiceInterface {
1111
isResendCodeRequest: boolean,
1212
userLanguage: string,
1313
req: Request,
14+
mfaMethodId: string,
1415
journeyType?: JOURNEY_TYPE
1516
) => Promise<ApiResponseResult<DefaultApiResponse>>;
1617
}

src/components/enter-mfa/enter-mfa-controller.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ import { NOTIFICATION_TYPE, PATH_NAMES } from "../../app.constants.js";
33
import type { VerifyCodeInterface } from "../common/verify-code/types.js";
44
import { codeService } from "../common/verify-code/verify-code-service.js";
55
import { verifyCodePost } from "../common/verify-code/verify-code-controller.js";
6-
import type {
7-
ExpressRouteFunc,
8-
MfaMethod,
9-
SmsMfaMethod,
10-
} from "../../types.js";
6+
import type { ExpressRouteFunc, MfaMethod, SmsMfaMethod } from "../../types.js";
117
import type { SecurityCodeErrorType } from "../common/constants.js";
128
import { ERROR_CODES } from "../common/constants.js";
139
import type { AccountRecoveryInterface } from "../common/account-recovery/types.js";

src/components/enter-password/enter-password-controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export function enterPasswordPost(
205205
false,
206206
xss(req.cookies.lng as string),
207207
req,
208+
req.session.user.activeMfaMethodId,
208209
journeyType
209210
);
210211

src/components/enter-password/tests/enter-password-controller.test.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ describe("enter password controller", () => {
3434
let req: RequestOutput;
3535
let res: ResponseOutput;
3636
const { email } = commonVariables;
37+
const DEFAULT_MFA_METHOD_ID = "test-id1";
3738

3839
beforeEach(() => {
3940
req = createMockRequest(PATH_NAMES.ENTER_PASSWORD);
@@ -72,7 +73,7 @@ describe("enter password controller", () => {
7273
mfaMethodType: "SMS",
7374
passwordChangeRequired: false,
7475
mfaMethods: buildMfaMethods({
75-
id: "test-id1",
76+
id: DEFAULT_MFA_METHOD_ID,
7677
phoneNumber: "07123456789",
7778
}),
7879
},
@@ -106,7 +107,7 @@ describe("enter password controller", () => {
106107
mfaMethodType: "SMS",
107108
passwordChangeRequired: false,
108109
mfaMethods: buildMfaMethods({
109-
id: "test-id1",
110+
id: DEFAULT_MFA_METHOD_ID,
110111
phoneNumber: "07123456789",
111112
}),
112113
},
@@ -157,6 +158,7 @@ describe("enter password controller", () => {
157158
sinon.match.any,
158159
sinon.match.any,
159160
sinon.match.any,
161+
DEFAULT_MFA_METHOD_ID,
160162
JOURNEY_TYPE.REAUTHENTICATION
161163
);
162164
});
@@ -197,7 +199,7 @@ describe("enter password controller", () => {
197199
mfaMethodVerified: true,
198200
mfaMethodType: "SMS",
199201
mfaMethods: buildMfaMethods({
200-
id: "test-id1",
202+
id: DEFAULT_MFA_METHOD_ID,
201203
phoneNumber: "07123456789",
202204
}),
203205
},
@@ -310,7 +312,7 @@ describe("enter password controller", () => {
310312
mfaMethodVerified: true,
311313
mfaMethodType: "SMS",
312314
mfaMethods: buildMfaMethods({
313-
id: "test-id1",
315+
id: DEFAULT_MFA_METHOD_ID,
314316
phoneNumber: "07123456789",
315317
}),
316318
},
@@ -362,7 +364,7 @@ describe("enter password controller", () => {
362364
mfaMethodVerified: true,
363365
mfaMethodType: "SMS",
364366
mfaMethods: buildMfaMethods({
365-
id: "test-id1",
367+
id: DEFAULT_MFA_METHOD_ID,
366368
phoneNumber: "07123456789",
367369
}),
368370
},
@@ -430,7 +432,7 @@ describe("enter password controller", () => {
430432
mfaMethodVerified: true,
431433
mfaMethodType: "SMS",
432434
mfaMethods: buildMfaMethods({
433-
id: "test-id1",
435+
id: DEFAULT_MFA_METHOD_ID,
434436
phoneNumber: "07123456789",
435437
}),
436438
},
@@ -455,7 +457,7 @@ describe("enter password controller", () => {
455457
mfaMethodVerified: false,
456458
mfaMethodType: "SMS",
457459
mfaMethods: buildMfaMethods({
458-
id: "test-id1",
460+
id: DEFAULT_MFA_METHOD_ID,
459461
phoneNumber: "07123456789",
460462
}),
461463
},
@@ -480,7 +482,7 @@ describe("enter password controller", () => {
480482
mfaMethodVerified: true,
481483
mfaMethodType: "SMS",
482484
mfaMethods: buildMfaMethods({
483-
id: "test-id1",
485+
id: DEFAULT_MFA_METHOD_ID,
484486
phoneNumber: "07123456789",
485487
}),
486488
},
@@ -533,7 +535,7 @@ describe("enter password controller", () => {
533535
mfaMethodType: "SMS",
534536
passwordChangeRequired: true,
535537
mfaMethods: buildMfaMethods({
536-
id: "test-id1",
538+
id: DEFAULT_MFA_METHOD_ID,
537539
phoneNumber: "07123456789",
538540
}),
539541
},
Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import type { Request, Response } from "express";
22
import { MFA_METHOD_TYPE, PATH_NAMES } from "../../app.constants.js";
3-
import type { MfaMethod } from "../../types.js";
4-
import { getNextPathAndUpdateJourney } from "../common/constants.js";
3+
import type { ExpressRouteFunc, MfaMethod } from "../../types.js";
4+
import {
5+
ERROR_CODES,
6+
getErrorPathByCode,
7+
getNextPathAndUpdateJourney,
8+
} from "../common/constants.js";
59
import { USER_JOURNEY_EVENTS } from "../common/state-machine/state-machine.js";
10+
import type { MfaServiceInterface } from "../common/mfa/types.js";
11+
import { mfaService } from "../common/mfa/mfa-service.js";
12+
import xss from "xss";
13+
import { getJourneyTypeFromUserSession } from "../common/journey/journey.js";
14+
import { BadRequestError } from "../../utils/error.js";
615

716
export function sortMfaMethodsBackupFirst(
817
mfaMethods: MfaMethod[]
@@ -22,24 +31,71 @@ export function howDoYouWantSecurityCodesGet(
2231
});
2332
}
2433

25-
export async function howDoYouWantSecurityCodesPost(
26-
req: Request,
27-
res: Response
28-
): Promise<void> {
29-
const mfaMethodId = req.body["mfa-method-id"];
30-
const selectedMethod = req.session.user.mfaMethods.find(
31-
(method: MfaMethod) => method.id === mfaMethodId
32-
);
33-
if (selectedMethod.type === MFA_METHOD_TYPE.SMS) {
34-
req.session.user.activeMfaMethodId = mfaMethodId;
35-
res.redirect(
36-
await getNextPathAndUpdateJourney(
37-
req,
38-
req.path,
39-
USER_JOURNEY_EVENTS.SELECT_SMS_MFA_METHOD,
40-
null,
41-
res.locals.sessionId
42-
)
34+
export function howDoYouWantSecurityCodesPost(
35+
mfaCodeService: MfaServiceInterface = mfaService()
36+
): ExpressRouteFunc {
37+
return async function (req: Request, res: Response) {
38+
const mfaMethodId = req.body["mfa-method-id"];
39+
const selectedMethod = req.session.user.mfaMethods.find(
40+
(method: MfaMethod) => method.id === mfaMethodId
4341
);
44-
}
42+
43+
if (selectedMethod.type === MFA_METHOD_TYPE.SMS) {
44+
if (mfaMethodId !== req.session.user.activeMfaMethodId) {
45+
req.session.user.activeMfaMethodId = mfaMethodId;
46+
const { email } = req.session.user;
47+
const { sessionId, clientSessionId, persistentSessionId } = res.locals;
48+
49+
const result = await mfaCodeService.sendMfaCode(
50+
sessionId,
51+
clientSessionId,
52+
email,
53+
persistentSessionId,
54+
false,
55+
xss(req.cookies.lng as string),
56+
req,
57+
req.session.user.activeMfaMethodId,
58+
getJourneyTypeFromUserSession(req.session.user, {
59+
includeReauthentication: true,
60+
})
61+
);
62+
63+
if (!result.success) {
64+
if (result.data.code === ERROR_CODES.MFA_CODE_REQUESTS_BLOCKED) {
65+
return res.render("security-code-error/index-wait.njk");
66+
}
67+
68+
if (result.data.code === ERROR_CODES.ENTERED_INVALID_MFA_MAX_TIMES) {
69+
return res.render(
70+
"security-code-error/index-security-code-entered-exceeded.njk",
71+
{
72+
show2HrScreen: true,
73+
contentId: "727a0395-cc00-48eb-a411-bfe9d8ac5fc8",
74+
}
75+
);
76+
}
77+
78+
const path = getErrorPathByCode(result.data.code);
79+
80+
if (path) {
81+
return res.redirect(path);
82+
}
83+
84+
throw new BadRequestError(result.data.message, result.data.code);
85+
}
86+
87+
return res.redirect(
88+
await getNextPathAndUpdateJourney(
89+
req,
90+
req.path,
91+
USER_JOURNEY_EVENTS.SELECT_SMS_MFA_METHOD,
92+
null,
93+
res.locals.sessionId
94+
)
95+
);
96+
}
97+
}
98+
99+
throw new BadRequestError("Unexpected MFA Type", 500);
100+
};
45101
}

src/components/how-do-you-want-security-codes/how-do-you-want-security-codes-routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ router.post(
2222
validateSessionMiddleware,
2323
allowUserJourneyMiddleware,
2424
validateHowDoYouWantSecurityCodesRequest(),
25-
howDoYouWantSecurityCodesPost
25+
howDoYouWantSecurityCodesPost()
2626
);
2727

2828
export { router as howDoYouWantSecurityCodesRouter };

src/components/how-do-you-want-security-codes/tests/how-do-you-want-security-codes-controller.test.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { MFA_METHOD_TYPE, PATH_NAMES } from "../../../app.constants.js";
1212
import { sinon } from "../../../../test/utils/test-utils.js";
1313
import { MfaMethodPriority } from "../../../types.js";
1414
import type { MfaMethod } from "../../../types.js";
15+
import type { MfaServiceInterface } from "../../common/mfa/types.js";
1516

1617
describe("how do you want security codes controller", () => {
1718
let req: RequestOutput;
@@ -57,12 +58,23 @@ describe("how do you want security codes controller", () => {
5758
},
5859
] as MfaMethod[];
5960

60-
req.body["mfa-method-id"] = backupId;
61+
for (const mfaMethodId of [defaultId, backupId]) {
62+
const fakeMfaCodeService: MfaServiceInterface = {
63+
sendMfaCode: sinon.fake.returns({
64+
success: true,
65+
}),
66+
} as unknown as MfaServiceInterface;
6167

62-
await howDoYouWantSecurityCodesPost(req as Request, res as Response);
68+
req.body["mfa-method-id"] = mfaMethodId;
6369

64-
expect(res.redirect).to.have.been.calledWith(PATH_NAMES.ENTER_MFA);
65-
expect(req.session.user.activeMfaMethodId).to.equal(backupId);
70+
await howDoYouWantSecurityCodesPost(fakeMfaCodeService)(
71+
req as Request,
72+
res as Response
73+
);
74+
75+
expect(res.redirect).to.have.been.calledWith(PATH_NAMES.ENTER_MFA);
76+
expect(req.session.user.activeMfaMethodId).to.equal(mfaMethodId);
77+
}
6678
});
6779
});
6880
});

0 commit comments

Comments
 (0)