Skip to content

Commit

Permalink
Merge pull request #1357 from govuk-one-login/AUT-2093-part-2
Browse files Browse the repository at this point in the history
AUT-2093-part-2: Create Re-enter your sign in details screen
  • Loading branch information
ayoshebby authored Feb 14, 2024
2 parents 3382f46 + ef36658 commit d8394c8
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 3 deletions.
42 changes: 39 additions & 3 deletions src/components/enter-email/enter-email-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,58 @@ import { SendNotificationServiceInterface } from "../common/send-notification/ty
import { sendNotificationService } from "../common/send-notification/send-notification-service";
import { USER_JOURNEY_EVENTS } from "../common/state-machine/state-machine";
import xss from "xss";
import { CheckReauthServiceInterface } from "../check-reauth-users/types";
import { checkReauthUsersService } from "../check-reauth-users/check-reauth-users-service";
import { supportReauthentication } from "../../config";
import {
formatValidationError,
renderBadRequest,
} from "../../utils/validation";

const ENTER_EMAIL_TEMPLATE = "enter-email/index-existing-account.njk";
const RE_ENTER_EMAIL_TEMPLATE = "enter-email/index-re-enter-email-account.njk";

export function enterEmailGet(req: Request, res: Response): void {
return res.render("enter-email/index-existing-account.njk");
const isReAuthenticationRequired = req.session.user.reauthenticate;

if (supportReauthentication() && isReAuthenticationRequired) {
return res.render(RE_ENTER_EMAIL_TEMPLATE);
}

return res.render(ENTER_EMAIL_TEMPLATE);
}

export function enterEmailCreateGet(req: Request, res: Response): void {
return res.render("enter-email/index-create-account.njk");
}

export function enterEmailPost(
service: EnterEmailServiceInterface = enterEmailService()
service: EnterEmailServiceInterface = enterEmailService(),
checkReauthService: CheckReauthServiceInterface = checkReauthUsersService()
): ExpressRouteFunc {
return async function (req: Request, res: Response) {
const email = req.body.email;
const sessionId = res.locals.sessionId;
const { sessionId, clientSessionId, persistentSessionId } = res.locals;
req.session.user.email = email.toLowerCase();
const isReAuthenticationRequired = req.session.user.reauthenticate;

if (supportReauthentication() && isReAuthenticationRequired) {
const checkReauth = await checkReauthService.checkReauthUsers(
sessionId,
email,
req.ip,
clientSessionId,
persistentSessionId
);

if (!checkReauth.success) {
const error = formatValidationError(
"email",
req.t("pages.reEnterEmailAccount.enterYourEmailAddressError")
);
return renderBadRequest(res, req, RE_ENTER_EMAIL_TEMPLATE, error);
}
}

const result = await service.userExists(
sessionId,
Expand Down
48 changes: 48 additions & 0 deletions src/components/enter-email/index-re-enter-email-account.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{% extends "common/layout/base.njk" %}
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/button/macro.njk" import govukButton %}
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
{% from "govuk/components/inset-text/macro.njk" import govukInsetText %}
{% set showBack = true %}
{% set hrefBack = 'sign-in-or-create' %}
{% set pageTitleName = 'pages.reEnterEmailAccount.title' | translate %}
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}

{% block content %}

{% include "common/errors/errorSummary.njk" %}

<form action="/enter-email" method="post" novalidate>

<input type="hidden" name="_csrf" value="{{csrfToken}}"/>

<h1 class="govuk-heading-l">
{{ 'pages.reEnterEmailAccount.header' | translate }}
</h1>

<p class="govuk-body">{{'pages.reEnterEmailAccount.paragraph1' | translate}}</p>

{{ govukInput({
label: {
text: 'pages.reEnterEmailAccount.enterYourEmailAddress' | translate
},
id: "email",
name: "email",
value: email,
type: "email",
autocomplete: "email",
spellcheck: false,
errorMessage: {
text: errors['email'].text
} if (errors['email'])})
}}

{{ govukButton({
"text": "general.continue.label" | translate,
"type": "Submit",
"preventDoubleClick": true
}) }}

</form>

{% endblock %}
119 changes: 119 additions & 0 deletions src/components/enter-email/tests/enter-email-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
RequestOutput,
ResponseOutput,
} from "mock-req-res";
import { CheckReauthServiceInterface } from "../../check-reauth-users/types";

describe("enter email controller", () => {
let req: RequestOutput;
Expand All @@ -28,6 +29,7 @@ describe("enter email controller", () => {
req = mockRequest({
session: { client: {}, user: {} },
log: { info: sinon.fake() },
i18n: { language: "en" },
});
res = mockResponse();
});
Expand All @@ -46,6 +48,49 @@ describe("enter email controller", () => {
"enter-email/index-create-account.njk"
);
});

it("should render enter email view when supportReauthentication flag is switched off", async () => {
process.env.SUPPORT_REAUTHENTICATION = "0";

await enterEmailGet(req as Request, res as Response);

expect(res.render).to.have.calledWith(
"enter-email/index-existing-account.njk"
);
});

it("should render enter email view when isReautheticationRequired is false", async () => {
process.env.SUPPORT_REAUTHENTICATION = "1";
res.locals.sessionId = "123456-djjad";
res.locals.clientSessionId = "00000-djjad";
res.locals.persistentSessionId = "dips-123456-abc";
req.session.user = {
email: "[email protected]",
};

await enterEmailGet(req as Request, res as Response);

expect(res.render).to.have.calledWith(
"enter-email/index-existing-account.njk"
);
});

it("should render enter password view when isReautheticationRequired is true and check service returns successfully", async () => {
process.env.SUPPORT_REAUTHENTICATION = "1";
res.locals.sessionId = "123456-djjad";
res.locals.clientSessionId = "00000-djjad";
res.locals.persistentSessionId = "dips-123456-abc";
req.session.user = {
email: "[email protected]",
reauthenticate: "12345",
};

await enterEmailGet(req as Request, res as Response);

expect(res.render).to.have.calledWith(
"enter-email/index-re-enter-email-account.njk"
);
});
});

describe("enterEmailGet", () => {
Expand Down Expand Up @@ -149,6 +194,80 @@ describe("enter email controller", () => {
);
expect(fakeService.userExists).to.have.been.calledOnce;
});

it("should redirect to /enter-email when re-authentication is required and re-auth check is unsuccessful", async () => {
process.env.SUPPORT_REAUTHENTICATION = "1";

req.body.email = "test.test.com";
res.locals.sessionId = "dsad.dds";
req.path = PATH_NAMES.ENTER_EMAIL_SIGN_IN;
res.locals.sessionId = "123456-djjad";
res.locals.clientSessionId = "00000-djjad";
res.locals.persistentSessionId = "dips-123456-abc";
req.session.user = {
email: "[email protected]",
reauthenticate: "12345",
};

req.t = sinon.fake.returns("translated string");

const fakeUserExistsService: EnterEmailServiceInterface = {
userExists: sinon.fake.returns({
success: false,
data: { doesUserExist: false },
}),
} as unknown as EnterEmailServiceInterface;

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

await enterEmailPost(fakeUserExistsService, fakeCheckReauthService)(
req as Request,
res as Response
);

expect(fakeCheckReauthService.checkReauthUsers).to.have.been.calledOnce;
expect(res.render).to.have.calledWith(
"enter-email/index-re-enter-email-account.njk"
);
});

it("should redirect to /enter-password re-auth page when re-authentication is required and service call is successful", async () => {
process.env.SUPPORT_REAUTHENTICATION = "1";
req.body.email = "test.test.com";
res.locals.sessionId = "dsad.dds";
req.path = PATH_NAMES.ENTER_EMAIL_SIGN_IN;
res.locals.sessionId = "123456-djjad";
res.locals.clientSessionId = "00000-djjad";
res.locals.persistentSessionId = "dips-123456-abc";
req.session.user = {
email: "[email protected]",
reauthenticate: "12345",
};

const fakeService: EnterEmailServiceInterface = {
userExists: sinon.fake.returns({
success: true,
data: { doesUserExist: true },
}),
} 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
);

expect(res.redirect).to.have.calledWith(PATH_NAMES.ENTER_PASSWORD);
});
});

describe("enterEmailCreatePost", () => {
Expand Down
48 changes: 48 additions & 0 deletions src/components/enter-email/tests/enter-email-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
HTTP_STATUS_CODES,
PATH_NAMES,
} from "../../../app.constants";
import { CheckReauthServiceInterface } from "../../check-reauth-users/types";
import { AxiosResponse } from "axios";
import { createApiResponse } from "../../../utils/http";
import { DefaultApiResponse } from "../../../types";

describe("Integration::enter email", () => {
let token: string | string[];
Expand All @@ -20,6 +24,7 @@ describe("Integration::enter email", () => {
decache("../../../app");
decache("../../../middleware/session-middleware");
const sessionMiddleware = require("../../../middleware/session-middleware");
const checkReauthUsersService = require("../../check-reauth-users/check-reauth-users-service");

sinon
.stub(sessionMiddleware, "validateSessionMiddleware")
Expand All @@ -31,11 +36,26 @@ describe("Integration::enter email", () => {
nextPath: PATH_NAMES.ENTER_EMAIL_SIGN_IN,
optionalPaths: [PATH_NAMES.SIGN_IN_OR_CREATE],
},
reauthenticate: "12345",
};

next();
});

sinon
.stub(checkReauthUsersService, "checkReauthUsersService")
.callsFake((): CheckReauthServiceInterface => {
async function checkReauthUsers() {
const fakeAxiosResponse: AxiosResponse = {
status: HTTP_STATUS_CODES.OK,
} as AxiosResponse;

return createApiResponse<DefaultApiResponse>(fakeAxiosResponse);
}

return { checkReauthUsers };
});

app = await require("../../../app").createApp();
baseApi = process.env.FRONTEND_API_BASE_URL;

Expand Down Expand Up @@ -199,4 +219,32 @@ describe("Integration::enter email", () => {
})
.expect(500, done);
});

it("should redirect to /enter-password page when email address exists and check re-auth users api call is successfully", (done) => {
process.env.SUPPORT_REAUTHENTICATION = "1";

nock(baseApi)
.post(API_ENDPOINTS.CHECK_REAUTH_USERS)
.once()
.reply(HTTP_STATUS_CODES.OK);

nock(baseApi)
.post(API_ENDPOINTS.USER_EXISTS)
.once()
.reply(HTTP_STATUS_CODES.OK, {
email: "[email protected]",
doesUserExist: true,
});

request(app)
.post(PATH_NAMES.ENTER_EMAIL_SIGN_IN)
.type("form")
.set("Cookie", cookies)
.send({
_csrf: token,
email: "[email protected]",
})
.expect("Location", PATH_NAMES.ENTER_PASSWORD)
.expect(302, done);
});
});
7 changes: 7 additions & 0 deletions src/locales/cy/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@
}
}
},
"reEnterEmailAccount": {
"title": "Rhowch eich manylion mewngofnodi i mewn eto i barhau",
"header": "Rhowch eich manylion mewngofnodi i mewn eto i barhau",
"paragraph1": "Er diogelwch, mewngofnodwch gyda GOV.UK One Login eto.",
"enterYourEmailAddress": "Rhowch eich cyfeiriad e-bost",
"enterYourEmailAddressError": "Rhowch yr un cyfeiriad e-bost rydych wedi’i ddefnyddio i fewngofnodi"
},
"accountNotFoundOneLogin": {
"title": "Ni ddarganfyddwyd GOV.UK One Login",
"header": "Ni ddarganfyddwyd GOV.UK One Login",
Expand Down
7 changes: 7 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@
}
}
},
"reEnterEmailAccount": {
"title": "Re-enter your sign-in details to continue",
"header": "Re-enter your sign-in details to continue",
"paragraph1": "For security, sign in with GOV.UK One Login again.",
"enterYourEmailAddress": "Enter your email address",
"enterYourEmailAddressError": "Enter the same email address you used to sign in"
},
"accountNotFoundOneLogin": {
"title": "No GOV.UK One Login found",
"header": "No GOV.UK One Login found",
Expand Down

0 comments on commit d8394c8

Please sign in to comment.