From c5082549b98e7ca8572c1618a41b45bdea9b9536 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Sun, 18 May 2025 10:12:24 +0100 Subject: [PATCH 01/11] AUT-4297: Add generic_app channel Accept 'generic_app' as a valid channel on the FE and render the same mobile pages as the strategic app when res.locals.genericApp is true. Have added an isApp res.locals param that is true if genericApp or strategicApp is true. This value is then used in templates. --- src/app.constants.ts | 1 + .../account-not-found-controller.ts | 2 +- .../get-account-not-found-template.ts | 4 ++-- .../account-not-found-controller.test.ts | 12 ++++++----- .../get-account-not-found-template.test.ts | 4 ++-- .../sign-in-or-create-controller.ts | 2 +- .../sign-in-or-create-controller.test.ts | 15 +++++++------ src/config.ts | 6 +++++- src/middleware/channel-middleware.ts | 3 +++ .../tests/channel-middleware.test.ts | 21 ++++++++++++++++++- src/utils/get-channel-specific-template.ts | 4 ++-- .../get-channel-specific-template.test.ts | 4 ++-- 12 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/app.constants.ts b/src/app.constants.ts index faad4960aa..73dab1f491 100644 --- a/src/app.constants.ts +++ b/src/app.constants.ts @@ -244,6 +244,7 @@ export enum JOURNEY_TYPE { export enum CHANNEL { WEB = "web", STRATEGIC_APP = "strategic_app", + GENERIC_APP = "generic_app", } export const ENVIRONMENT_NAME = { diff --git a/src/components/account-not-found/account-not-found-controller.ts b/src/components/account-not-found/account-not-found-controller.ts index f10ace29d2..ee52739ad8 100644 --- a/src/components/account-not-found/account-not-found-controller.ts +++ b/src/components/account-not-found/account-not-found-controller.ts @@ -17,7 +17,7 @@ export function accountNotFoundGet(req: Request, res: Response): void { const template: string = getAccountNotFoundTemplate( clientIsOneLogin(req), req.session.client.serviceType, - res.locals.strategicAppChannel + res.locals.isApp ); res.render(template, { diff --git a/src/components/account-not-found/get-account-not-found-template.ts b/src/components/account-not-found/get-account-not-found-template.ts index c8f0a7d898..990c8d130a 100644 --- a/src/components/account-not-found/get-account-not-found-template.ts +++ b/src/components/account-not-found/get-account-not-found-template.ts @@ -6,7 +6,7 @@ import { getChannelSpecificTemplate } from "../../utils/get-channel-specific-tem export function getAccountNotFoundTemplate( isOneLoginService: boolean, serviceType: string, - isStrategicAppChannel: boolean + isApp: boolean ): string { let webTemplate; @@ -20,7 +20,7 @@ export function getAccountNotFoundTemplate( return getChannelSpecificTemplate( webTemplate, - isStrategicAppChannel, + isApp, WEB_TO_MOBILE_TEMPLATE_MAPPINGS ); } diff --git a/src/components/account-not-found/tests/account-not-found-controller.test.ts b/src/components/account-not-found/tests/account-not-found-controller.test.ts index 49d5091945..dab62e1099 100644 --- a/src/components/account-not-found/tests/account-not-found-controller.test.ts +++ b/src/components/account-not-found/tests/account-not-found-controller.test.ts @@ -25,7 +25,7 @@ describe("account not found controller", () => { }); describe("accountNotFoundGet", () => { - describe("when strategicAppChannel is not defined", () => { + describe("when isApp is not defined", () => { it("should render the account not found mandatory view when serviceType undefined", () => { accountNotFoundGet(req, res); @@ -53,9 +53,9 @@ describe("account not found controller", () => { }); }); - describe("when strategicAppChannel is false", () => { + describe("when isApp is false", () => { beforeEach(() => { - res.locals.strategicAppChannel = false; + res.locals.isApp = false; }); it("should render the account not found mandatory view when serviceType undefined", () => { @@ -85,9 +85,9 @@ describe("account not found controller", () => { }); }); - describe("when strategicAppChannel is true", () => { + describe("when isApp is true", () => { beforeEach(() => { - res.locals.strategicAppChannel = true; + res.locals.isApp = true; }); it("should render the account not found mandatory view when serviceType undefined", () => { @@ -100,6 +100,7 @@ describe("account not found controller", () => { it("should render the account not found optional view when serviceType optional", () => { req.session.client.serviceType = SERVICE_TYPE.OPTIONAL; + accountNotFoundGet(req, res); expect(res.render).to.have.calledWith( @@ -109,6 +110,7 @@ describe("account not found controller", () => { it("should render the account not found optional view when the service is part of One Login", () => { req.session.client.isOneLoginService = true; + accountNotFoundGet(req, res); expect(res.render).to.have.calledWith( diff --git a/src/components/account-not-found/tests/get-account-not-found-template.test.ts b/src/components/account-not-found/tests/get-account-not-found-template.test.ts index 53e648d7ff..2a41f5ce9b 100644 --- a/src/components/account-not-found/tests/get-account-not-found-template.test.ts +++ b/src/components/account-not-found/tests/get-account-not-found-template.test.ts @@ -3,7 +3,7 @@ import { describe } from "mocha"; import { getAccountNotFoundTemplate } from "../get-account-not-found-template.js"; import { SERVICE_TYPE } from "../../../app.constants.js"; describe("getAccountNotFoundWebTemplate", () => { - describe("when isStrategicApp is false", () => { + describe("when isApp is false", () => { describe("when isOneLoginService is true", () => { it("should always return 'account-not-found/index-one-login.njk'", () => { [SERVICE_TYPE.OPTIONAL, SERVICE_TYPE.MANDATORY].forEach((i) => { @@ -31,7 +31,7 @@ describe("getAccountNotFoundWebTemplate", () => { }); }); - describe("when isStrategicApp is true", () => { + describe("when isApp is true", () => { describe("when isOneLoginService is true", () => { it("should always return 'account-not-found/index-mobile.njk'", () => { [SERVICE_TYPE.OPTIONAL, SERVICE_TYPE.MANDATORY].forEach((i) => { diff --git a/src/components/sign-in-or-create/sign-in-or-create-controller.ts b/src/components/sign-in-or-create/sign-in-or-create-controller.ts index b2dd07005c..32ef3870dd 100644 --- a/src/components/sign-in-or-create/sign-in-or-create-controller.ts +++ b/src/components/sign-in-or-create/sign-in-or-create-controller.ts @@ -16,7 +16,7 @@ export async function signInOrCreateGet( const template = getChannelSpecificTemplate( "sign-in-or-create/index.njk", - res.locals.strategicAppChannel, + res.locals.isApp, WEB_TO_MOBILE_TEMPLATE_MAPPINGS ); diff --git a/src/components/sign-in-or-create/tests/sign-in-or-create-controller.test.ts b/src/components/sign-in-or-create/tests/sign-in-or-create-controller.test.ts index 391bf7e129..497c5d628c 100644 --- a/src/components/sign-in-or-create/tests/sign-in-or-create-controller.test.ts +++ b/src/components/sign-in-or-create/tests/sign-in-or-create-controller.test.ts @@ -32,18 +32,17 @@ describe("sign in or create controller", () => { expect(res.render).to.have.calledWith("sign-in-or-create/index.njk"); }); - describe("where the context is mobile", () => { - it("should render the mobile template", async () => { - res.locals.strategicAppChannel = true; + it("should render the mobile template when the channel is an app", async () => { + res.locals.isApp = true; - signInOrCreateGet(req as Request, res as Response); + signInOrCreateGet(req as Request, res as Response); - expect(res.render).to.have.calledWith( - "sign-in-or-create/index-mobile.njk" - ); - }); + expect(res.render).to.have.calledWith( + "sign-in-or-create/index-mobile.njk" + ); }); }); + describe("signInOrCreatePost", () => { it("should redirect to enter email new create account", async () => { req.body.optionSelected = "create"; diff --git a/src/config.ts b/src/config.ts index 594ed13457..29cee41f80 100644 --- a/src/config.ts +++ b/src/config.ts @@ -176,7 +176,11 @@ export function supportHttpKeepAlive(): boolean { } export function isValidChannel(channel: string): boolean { - return channel === CHANNEL.WEB || channel === CHANNEL.STRATEGIC_APP; + return ( + channel === CHANNEL.WEB || + channel === CHANNEL.STRATEGIC_APP || + channel === CHANNEL.GENERIC_APP + ); } export function showTestBanner(): boolean { diff --git a/src/middleware/channel-middleware.ts b/src/middleware/channel-middleware.ts index 1106141008..1cabfa5c5e 100644 --- a/src/middleware/channel-middleware.ts +++ b/src/middleware/channel-middleware.ts @@ -19,4 +19,7 @@ export function channelMiddleware( function setChannelFlags(res: Response, channel?: string): void { res.locals.strategicAppChannel = channel === CHANNEL.STRATEGIC_APP; res.locals.webChannel = channel === CHANNEL.WEB; + res.locals.genericAppChannel = channel === CHANNEL.GENERIC_APP; + res.locals.isApp = + channel === CHANNEL.STRATEGIC_APP || channel === CHANNEL.GENERIC_APP; } diff --git a/src/middleware/tests/channel-middleware.test.ts b/src/middleware/tests/channel-middleware.test.ts index ed329866ad..7ec7e280f2 100644 --- a/src/middleware/tests/channel-middleware.test.ts +++ b/src/middleware/tests/channel-middleware.test.ts @@ -18,7 +18,7 @@ describe("session-middleware", () => { next = sinon.fake(); }); - it("should set strategicAppChannel to true for strategic app clients", () => { + it("should set strategicAppChannel and isApp to true for strategic app clients", () => { const req = mockRequest({ session: { client: {}, user: { channel: "strategic_app" } }, }); @@ -26,6 +26,8 @@ describe("session-middleware", () => { expect(res.locals.strategicAppChannel).to.equal(true); expect(res.locals.webChannel).to.equal(false); + expect(res.locals.genericAppChannel).to.equal(false); + expect(res.locals.isApp).to.equal(true); expect(next).to.be.calledOnce; }); @@ -37,6 +39,21 @@ describe("session-middleware", () => { expect(res.locals.strategicAppChannel).to.equal(false); expect(res.locals.webChannel).to.equal(true); + expect(res.locals.genericAppChannel).to.equal(false); + expect(res.locals.isApp).to.equal(false); + expect(next).to.be.calledOnce; + }); + + it("should set generic-app and isApp to true for generic-app clients", () => { + const req = mockRequest({ + session: { client: {}, user: { channel: "generic_app" } }, + }); + channelMiddleware(req as Request, res as Response, next); + + expect(res.locals.strategicAppChannel).to.equal(false); + expect(res.locals.webChannel).to.equal(false); + expect(res.locals.genericAppChannel).to.equal(true); + expect(res.locals.isApp).to.equal(true); expect(next).to.be.calledOnce; }); @@ -65,6 +82,7 @@ describe("session-middleware", () => { expect(res.locals.strategicAppChannel).to.equal(true); expect(res.locals.webChannel).to.equal(false); + expect(res.locals.genericAppChannel).to.equal(false); expect(next).to.be.calledOnce; }); @@ -75,6 +93,7 @@ describe("session-middleware", () => { expect(res.locals.strategicAppChannel).to.equal(false); expect(res.locals.webChannel).to.equal(true); + expect(res.locals.genericAppChannel).to.equal(false); expect(next).to.be.calledOnce; }); }); diff --git a/src/utils/get-channel-specific-template.ts b/src/utils/get-channel-specific-template.ts index d9acc294cd..b6ea8e13c8 100644 --- a/src/utils/get-channel-specific-template.ts +++ b/src/utils/get-channel-specific-template.ts @@ -1,10 +1,10 @@ import { logger } from "./logger.js"; export function getChannelSpecificTemplate( webTemplateAndPath: string, - isStrategicAppChannel: boolean, + isApp: boolean, templateMappings: Record ): string { - if (!isStrategicAppChannel) { + if (!isApp) { return webTemplateAndPath; } diff --git a/test/unit/utils/get-channel-specific-template.test.ts b/test/unit/utils/get-channel-specific-template.test.ts index 41afdf6dd6..58812d9554 100644 --- a/test/unit/utils/get-channel-specific-template.test.ts +++ b/test/unit/utils/get-channel-specific-template.test.ts @@ -6,7 +6,7 @@ const mappings = { }; describe("getChannelSpecificTemplate", () => { - describe("where the channel is not mobile", () => { + describe("where the channel is not an app", () => { describe("and the webTemplateAndPath is not mapped", () => { it("should return the original webTemplateAndPath", () => { expect( @@ -23,7 +23,7 @@ describe("getChannelSpecificTemplate", () => { }); }); - describe("where the channel is mobile", () => { + describe("where the channel is an app", () => { describe("and the webTemplateAndPath is not mapped", () => { it("should return the original webTemplateAndPath", () => { expect( From 0a9fea934122717a9187b3f267eda843f8fb0228 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Mon, 19 May 2025 19:44:47 +0100 Subject: [PATCH 02/11] AUT-4297: Add generic_app channel to base.njk - When genericApp is true content is now the same in base.njk as when strategicAppChannel is true. If either is true res.locals.isApp is set to true, passed to template and used as a conditional. --- src/components/common/layout/base.njk | 8 +-- .../layout/tests/base-integration.test.ts | 65 ++++++++++--------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/components/common/layout/base.njk b/src/components/common/layout/base.njk index 9944e5dfd0..262fa135cd 100644 --- a/src/components/common/layout/base.njk +++ b/src/components/common/layout/base.njk @@ -6,7 +6,7 @@ {% from "../header/macro.njk" import strategicAppHeader %} -{% if strategicAppChannel == true %} +{% if isApp %} {% set htmlClasses = 'govuk-template__mobile' %} {% endif %} @@ -53,7 +53,7 @@ {% set phaseBannerClasses = "test-banner" if showTestBanner %} {% block header %} - {% if strategicAppChannel %} + {% if isApp %} {{ strategicAppHeader({ useTudorCrown: true, classes: phaseBannerClasses @@ -90,7 +90,7 @@ {% block main %}
- {% if strategicAppChannel === true %} + {% if isApp %} {% else %} {{ govukPhaseBanner({ tag: { @@ -140,7 +140,7 @@ {% endblock %} {% block footer %} -{% if strategicAppChannel === true %} +{% if isApp %} {% else %} {{ govukFooter({ diff --git a/src/components/common/layout/tests/base-integration.test.ts b/src/components/common/layout/tests/base-integration.test.ts index 81294c42bf..147d884925 100644 --- a/src/components/common/layout/tests/base-integration.test.ts +++ b/src/components/common/layout/tests/base-integration.test.ts @@ -43,8 +43,11 @@ describe("Integration:: base page ", () => { res.locals.sessionId = "tDy103saszhcxbQq0-mjdzU854"; if (channel === CHANNEL.WEB) { res.locals.webChannel = true; - } else if (channel === CHANNEL.STRATEGIC_APP) { - res.locals.strategicAppChannel = true; + } else if ( + channel === CHANNEL.STRATEGIC_APP || + channel === CHANNEL.GENERIC_APP + ) { + res.locals.isApp = true; } req.session.client = { @@ -132,41 +135,45 @@ describe("Integration:: base page ", () => { }); }); - describe("Strategic App channel", () => { + describe("App (strategic app and generic app) channels", () => { + const testCases = [CHANNEL.STRATEGIC_APP, CHANNEL.GENERIC_APP]; let $: any; - before(async () => { - await setupApp(CHANNEL.STRATEGIC_APP, true); - const response = await request(app).get(PATH_NAMES.SIGN_IN_OR_CREATE); - expect(response.status).to.equal(200); - $ = cheerio.load(response.text); - }); - - it("should render the custom header with no links", async () => { - expect($("a.govuk-header__link").length).to.equal(0); - expect($(".strategic-app-header").length).to.equal(1); - }); - it("should not render the footer", async () => { - expect($(".govuk-footer").length).to.equal(0); - }); - - describe("when in a non-production environment", () => { - it("should render the test phase header css", async () => { - expect($(".govuk-header").hasClass("test-banner")).to.be.true; - }); - }); - - describe("when in a production environment", () => { - let $: any; + testCases.forEach((channel) => { before(async () => { - await setupApp(CHANNEL.STRATEGIC_APP, false); + await setupApp(channel, true); const response = await request(app).get(PATH_NAMES.SIGN_IN_OR_CREATE); expect(response.status).to.equal(200); $ = cheerio.load(response.text); }); - it("should not render the test phase header css", async () => { - expect($(".govuk-header").hasClass("test-banner")).to.be.false; + it("should render the custom header with no links", async () => { + expect($("a.govuk-header__link").length).to.equal(0); + expect($(".strategic-app-header").length).to.equal(1); + }); + + it("should not render the footer", async () => { + expect($(".govuk-footer").length).to.equal(0); + }); + + describe("when in a non-production environment", () => { + it("should render the test phase header css", async () => { + expect($(".govuk-header").hasClass("test-banner")).to.be.true; + }); + }); + + describe("when in a production environment", () => { + let $: any; + before(async () => { + await setupApp(CHANNEL.STRATEGIC_APP, false); + const response = await request(app).get(PATH_NAMES.SIGN_IN_OR_CREATE); + expect(response.status).to.equal(200); + $ = cheerio.load(response.text); + }); + + it("should not render the test phase header css", async () => { + expect($(".govuk-header").hasClass("test-banner")).to.be.false; + }); }); }); }); From ac740c9712028358f1e9d4d4389393c1a77af9db Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Tue, 20 May 2025 06:32:21 +0100 Subject: [PATCH 03/11] AUT-4297: Add genericApp channel to enter email - res.locals.isApp is true if channel is generic_app or strategic_app - Also deleted explicitly passed params to res.render. - - These are not needed as res.locals are inherently passed to the template by res.render --- .../enter-email/enter-email-controller.ts | 10 +++------- .../enter-email/index-create-account.njk | 2 +- .../index-re-enter-email-account.njk | 4 ++-- .../tests/enter-email-controller.test.ts | 20 ++++++++----------- .../enter-password/index-account-locked.njk | 2 +- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/components/enter-email/enter-email-controller.ts b/src/components/enter-email/enter-email-controller.ts index 8941246592..6b7b98dc60 100644 --- a/src/components/enter-email/enter-email-controller.ts +++ b/src/components/enter-email/enter-email-controller.ts @@ -48,18 +48,14 @@ export function enterEmailGet(req: Request, res: Response): void { if (isLocked(req.session.user.wrongEmailEnteredLock)) { return res.render(BLOCKED_TEMPLATE); } - return res.render(RE_ENTER_EMAIL_TEMPLATE, { - isStrategicAppReauth: res.locals.strategicAppChannel, - }); + 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", { - strategicAppChannel: res.locals.strategicAppChannel, - }); + return res.render("enter-email/index-create-account.njk"); } export async function enterEmailCreateRequestGet( @@ -103,7 +99,7 @@ export function enterEmailPost( const CHANNEL_SPECIFIC_EMAIL_ERROR_KEY = getChannelSpecificErrorMessage( EMAIL_ERROR_KEY, - req.body.isStrategicAppReauth === "true", + res.locals.isApp, WEB_TO_MOBILE_ERROR_MESSAGE_MAPPINGS ); diff --git a/src/components/enter-email/index-create-account.njk b/src/components/enter-email/index-create-account.njk index 19b413c7d9..a57d7d3892 100644 --- a/src/components/enter-email/index-create-account.njk +++ b/src/components/enter-email/index-create-account.njk @@ -36,7 +36,7 @@

{{'pages.createPassword.termsOfUse.paragraph1' | translate}}

-{% if strategicAppChannel %} +{% if isApp %} {% set bullet1LinkText = 'pages.createPassword.termsOfUse.bullet1LinkTextApp' %} {% set bullet2LinkText = 'pages.createPassword.termsOfUse.bullet2LinkTextApp' %} {% else %} diff --git a/src/components/enter-email/index-re-enter-email-account.njk b/src/components/enter-email/index-re-enter-email-account.njk index 2675f7e823..697c02de4c 100644 --- a/src/components/enter-email/index-re-enter-email-account.njk +++ b/src/components/enter-email/index-re-enter-email-account.njk @@ -14,13 +14,13 @@
- +

{{ 'pages.reEnterEmailAccount.header' | translate }}

- {% if isStrategicAppReauth %} + {% if isApp %}

{{'mobileAppPages.reEnterEmailAccount.paragraph1' | translate}}

{% else %}

{{'pages.reEnterEmailAccount.paragraph1' | translate}}

diff --git a/src/components/enter-email/tests/enter-email-controller.test.ts b/src/components/enter-email/tests/enter-email-controller.test.ts index 776de80e8c..00a9cb1c45 100644 --- a/src/components/enter-email/tests/enter-email-controller.test.ts +++ b/src/components/enter-email/tests/enter-email-controller.test.ts @@ -55,19 +55,14 @@ describe("enter email controller", () => { req.session.user = { email }; }); - it("should render enter email create account view with the strategic app channel passed through when user selected create account", () => { - const STRATEGIC_APP_VALUES = [true, false]; - STRATEGIC_APP_VALUES.forEach((strategicAppChannel) => { - res.locals.strategicAppChannel = strategicAppChannel; - req.query.type = JOURNEY_TYPE.CREATE_ACCOUNT; + it("should render enter email create account view", () => { + req.query.type = JOURNEY_TYPE.CREATE_ACCOUNT; - enterEmailCreateGet(req as Request, res as Response); + enterEmailCreateGet(req as Request, res as Response); - expect(res.render).to.have.calledWith( - "enter-email/index-create-account.njk", - { strategicAppChannel: strategicAppChannel } - ); - }); + expect(res.render).to.have.calledWith( + "enter-email/index-create-account.njk" + ); }); it("should render enter email view when supportReauthentication flag is switched off", async () => { @@ -112,12 +107,13 @@ describe("enter email controller", () => { it("should render enter password view when isReautheticationRequired is true and check service returns successfully", async () => { process.env.SUPPORT_REAUTHENTICATION = "1"; + req.session.user = { email, reauthenticate: "12345", }; - await enterEmailGet(req as Request, res as Response); + enterEmailGet(req as Request, res as Response); expect(res.render).to.have.calledWith( "enter-email/index-re-enter-email-account.njk" diff --git a/src/components/enter-password/index-account-locked.njk b/src/components/enter-password/index-account-locked.njk index 02f8afc198..3f75e99c6a 100644 --- a/src/components/enter-password/index-account-locked.njk +++ b/src/components/enter-password/index-account-locked.njk @@ -17,7 +17,7 @@

{{'pages.accountLocked.bulletPointSection.title' | translate}}

  • {{'pages.accountLocked.bulletPointSection.first' | translate}}
  • - {% if strategicAppChannel === true %} + {% if isApp %}
  • {{'mobileAppPages.accountLocked.bulletPointSection.second' | translate}}
  • {% else %}
  • {{'pages.accountLocked.bulletPointSection.second' | translate}}
  • From 656eb39991f420067882f622c53b1ee0bb5a30b5 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Tue, 20 May 2025 06:52:25 +0100 Subject: [PATCH 04/11] AUT-4297: Add generic_app channel to mfa-reset-with-ipv - If res.locals.strategicApp or res.locals.genericApp res.locals.isApp is set to true - This is then passed to mfa-reset-with-ipv templates as a conditional for content. This ensures the same app content is shown for both strategic app and generic app channels. --- .../mfa-reset-with-ipv-controller.ts | 2 +- .../mfa-reset-with-ipv-controller.test.ts | 2 +- .../mfa-reset-with-ipv-integration.test.ts | 86 ++++++++++--------- 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/components/mfa-reset-with-ipv/mfa-reset-with-ipv-controller.ts b/src/components/mfa-reset-with-ipv/mfa-reset-with-ipv-controller.ts index 40f9664e37..eb6cc99bd3 100644 --- a/src/components/mfa-reset-with-ipv/mfa-reset-with-ipv-controller.ts +++ b/src/components/mfa-reset-with-ipv/mfa-reset-with-ipv-controller.ts @@ -19,7 +19,7 @@ export function mfaResetWithIpvGet( ); } - if (res.locals.strategicAppChannel === true) { + if (res.locals.isApp) { const redirectPath = await getNextPathAndUpdateJourney( req, req.path, diff --git a/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-controller.test.ts b/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-controller.test.ts index d9b49815dd..70fcc52b55 100644 --- a/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-controller.test.ts +++ b/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-controller.test.ts @@ -70,7 +70,7 @@ describe("mfa reset with ipv controller", () => { }); it("should allow redirect to new guidance page when user has come from a strategic app journey", async () => { - res.locals.strategicAppChannel = true; + res.locals.isApp = true; await mfaResetWithIpvGet(fakeMfaResetAuthorizeService(true))( req as Request, res as Response diff --git a/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-integration.test.ts b/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-integration.test.ts index eaa8e36443..2c07ee0df1 100644 --- a/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-integration.test.ts +++ b/src/components/mfa-reset-with-ipv/tests/mfa-reset-with-ipv-integration.test.ts @@ -58,26 +58,28 @@ describe("Mfa reset with ipv", () => { ); }); - it("should redirect to the new guidance page when using the strategic app", async () => { - const previousPath = PATH_NAMES.ENTER_AUTHENTICATOR_APP_CODE; - const app = await setupAppWithSessionMiddleware( - previousPath, - REQUEST_PATH, - true, - CHANNEL.STRATEGIC_APP - ); - - const expectedGuidancePage = PATH_NAMES.OPEN_IN_WEB_BROWSER; - - const agent = supertest.agent(app); - - await agent - .get(REQUEST_PATH) - .set("Cookie", "channel=strategic_app") - .expect(302) - .expect("Location", expectedGuidancePage); - - await agent.get(expectedGuidancePage).expect(200); + [CHANNEL.STRATEGIC_APP, CHANNEL.GENERIC_APP].forEach((channel) => { + it(`should redirect to the new guidance page when the channel is ${channel}`, async () => { + const previousPath = PATH_NAMES.ENTER_AUTHENTICATOR_APP_CODE; + const app = await setupAppWithSessionMiddleware( + previousPath, + REQUEST_PATH, + true, + channel + ); + + const expectedGuidancePage = PATH_NAMES.OPEN_IN_WEB_BROWSER; + + const agent = supertest.agent(app); + + await agent + .get(REQUEST_PATH) + .set("Cookie", `channel=${channel}`) + .expect(302) + .expect("Location", expectedGuidancePage); + + await agent.get(expectedGuidancePage).expect(200); + }); }); it("should return a 500 if account recovery is not permitted", async () => { @@ -102,25 +104,27 @@ describe("Mfa reset with ipv", () => { sinon.restore(); }); - it("should not render the page when coming from an arbitrary page", async () => { - const previousPath = PATH_NAMES.AUTHORIZE; - - const app = await setupAppWithSessionMiddleware( - previousPath, - REQUEST_PATH, - true, - CHANNEL.STRATEGIC_APP - ); - - await request( - app, - (test) => - test - .get(PATH_NAMES.OPEN_IN_WEB_BROWSER) - .expect(302) - .expect("Location", previousPath), - { expectAnalyticsPropertiesMatchSnapshot: false } - ); + [CHANNEL.STRATEGIC_APP, CHANNEL.GENERIC_APP].forEach((channel) => { + it(`should not render the page when coming from an arbitrary page and the channel is ${channel}`, async () => { + const previousPath = PATH_NAMES.AUTHORIZE; + + const app = await setupAppWithSessionMiddleware( + previousPath, + REQUEST_PATH, + true, + channel + ); + + await request( + app, + (test) => + test + .get(PATH_NAMES.OPEN_IN_WEB_BROWSER) + .expect(302) + .expect("Location", previousPath), + { expectAnalyticsPropertiesMatchSnapshot: false } + ); + }); }); }); @@ -158,7 +162,11 @@ describe("Mfa reset with ipv", () => { res.locals.sessionId = "tDy103saszhcxbQq0-mjdzU854"; res.locals.strategicAppChannel = channel === CHANNEL.STRATEGIC_APP; + res.locals.genericAppChannel = channel === CHANNEL.GENERIC_APP; res.locals.webChannel = channel == CHANNEL.WEB; + res.locals.isApp = + channel === CHANNEL.STRATEGIC_APP || + channel === CHANNEL.GENERIC_APP; req.session.user = { email: "test@test.com", From 053685a575a76847e8a1b1f2b1545b7f28b31bf1 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Tue, 20 May 2025 07:10:31 +0100 Subject: [PATCH 05/11] AUT-4297: Add genericApp to account created controller - If res.locals.strategicApp or res.locals.genericApp are true, res.locals.isApp is set to true - This is then passed to account created templates as a conditional for content. This ensures the same app content is shown for both strategic app and generic app channels. - The channel is set in the channel middleware when orch handover to auth. --- .../account-created/account-created-controller.ts | 1 - src/components/account-created/index.njk | 2 +- .../tests/account-created-controller.test.ts | 15 +-------------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/components/account-created/account-created-controller.ts b/src/components/account-created/account-created-controller.ts index d3e16cc882..bc15b232d3 100644 --- a/src/components/account-created/account-created-controller.ts +++ b/src/components/account-created/account-created-controller.ts @@ -7,7 +7,6 @@ export function accountCreatedGet(req: Request, res: Response): void { res.render("account-created/index.njk", { serviceType, name, - strategicAppChannel: res.locals.strategicAppChannel, }); } diff --git a/src/components/account-created/index.njk b/src/components/account-created/index.njk index e215eabc79..f06fa99e48 100644 --- a/src/components/account-created/index.njk +++ b/src/components/account-created/index.njk @@ -20,7 +20,7 @@ }) }} {% else %} - {% if strategicAppChannel === true %} + {% if isApp %}

    {{'mobileAppPages.accountCreated.text' | translate}}

    {% else %}

    {{'pages.accountCreated.text' | translate}}

    diff --git a/src/components/account-created/tests/account-created-controller.test.ts b/src/components/account-created/tests/account-created-controller.test.ts index 4cc3162e3c..a7e9077d37 100644 --- a/src/components/account-created/tests/account-created-controller.test.ts +++ b/src/components/account-created/tests/account-created-controller.test.ts @@ -34,21 +34,8 @@ describe("account created controller", () => { expect(res.render).to.have.been.calledWith("account-created/index.njk"); }); - - it("should pass through the strategicAppChannel", () => { - req.session.client.serviceType = "MANDATORY"; - req.session.client.name = "test client name"; - res.locals.strategicAppChannel = true; - - accountCreatedGet(req as Request, res as Response); - - expect(res.render).to.have.been.calledWith("account-created/index.njk", { - serviceType: "MANDATORY", - name: "test client name", - strategicAppChannel: true, - }); - }); }); + describe("accountCreatedPost", () => { it("should redirect to auth code", async () => { await accountCreatedPost(req as Request, res as Response); From fde3bb0f2456d156f09f07527c748071906512be Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Tue, 20 May 2025 12:18:52 +0100 Subject: [PATCH 06/11] AUT-4297: add genericApp channel to error handler - If res.locals.strategicApp or res.locals.genericApp are true, res.locals.isApp is set to true - This is then passed to error handler templates as a conditional for content. This ensures the same app content is shown for both strategic app and generic app channels. - The channel is set in the channel middleware when orch handover to auth. - Also deleted unnecessary explicit params passed to res.render. res.render inherently uses res.locals values when rendering templates. --- src/components/common/errors/session-expired.njk | 2 +- src/handlers/internal-server-error-handler.ts | 4 +--- test/unit/error-handler.test.ts | 16 ---------------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/components/common/errors/session-expired.njk b/src/components/common/errors/session-expired.njk index 647333235f..35396e974e 100644 --- a/src/components/common/errors/session-expired.njk +++ b/src/components/common/errors/session-expired.njk @@ -9,7 +9,7 @@

    {{ 'error.timeoutError.content.paragraph1' | translate }}

    {{ 'error.timeoutError.content.whatYouCanDo' | translate }}

    - {% if strategicAppChannel === true %} + {% if isApp %}

    {{ 'mobileAppError.timeoutError.content.paragraph2' | translate }}

    {% else %}

    {{ 'error.timeoutError.content.paragraph2' | translate }}

    diff --git a/src/handlers/internal-server-error-handler.ts b/src/handlers/internal-server-error-handler.ts index da88c27ba9..795f353ec9 100644 --- a/src/handlers/internal-server-error-handler.ts +++ b/src/handlers/internal-server-error-handler.ts @@ -23,9 +23,7 @@ export function serverErrorHandler( } if (res.statusCode == HTTP_STATUS_CODES.UNAUTHORIZED) { - return res.render("common/errors/session-expired.njk", { - strategicAppChannel: res.locals.strategicAppChannel, - }); + return res.render("common/errors/session-expired.njk"); } res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR); diff --git a/test/unit/error-handler.test.ts b/test/unit/error-handler.test.ts index c3b9b1ef18..160fe2487a 100644 --- a/test/unit/error-handler.test.ts +++ b/test/unit/error-handler.test.ts @@ -50,21 +50,5 @@ describe("Error handlers", () => { "common/errors/session-expired.njk" ); }); - - it("should pass through the strategicAppChannel", () => { - res.locals.strategicAppChannel = true; - - const err = new Error("timeout"); - res.statusCode = 401; - - serverErrorHandler(err, req as Request, res as Response, next); - - expect(res.render).to.have.been.calledOnceWith( - "common/errors/session-expired.njk", - { - strategicAppChannel: true, - } - ); - }); }); }); From 984aadbf29064030b101942796204bd83ed18e05 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Wed, 21 May 2025 09:54:43 +0100 Subject: [PATCH 07/11] AUT-4297: Add genericApp to security code error - If res.locals.strategicApp or res.locals.genericApp are true, res.locals.isApp is set to true - This is then passed to sec code error templates as a conditional for content. This ensures the same app content is shown for both strategic app and generic app channels. - The channel is set in the channel middleware when orch handover to auth. - Also deleted unnecessary explicit params passed to res.render. res.render inherently uses res.locals values when rendering templates. --- .../security-code-error/index-too-many-requests.njk | 2 +- .../security-code-error-controller.ts | 1 - .../tests/security-code-error-controller.test.ts | 13 ------------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/security-code-error/index-too-many-requests.njk b/src/components/security-code-error/index-too-many-requests.njk index ea98b78c43..fbaa751ce7 100644 --- a/src/components/security-code-error/index-too-many-requests.njk +++ b/src/components/security-code-error/index-too-many-requests.njk @@ -13,7 +13,7 @@

    {{ 'pages.securityRequestsExceededExpired.info.paragraph1' | translate }}

    {{ 'pages.securityRequestsExceededExpired.info.subHeading' | translate }}

    - {% if strategicAppChannel === true %} + {% if isApp %}

    {{ 'mobileAppPages.securityRequestsExceededExpired.info.paragraph2' | translate }}

    {% else %}

    {{ 'pages.securityRequestsExceededExpired.info.paragraph2' | translate }}

    diff --git a/src/components/security-code-error/security-code-error-controller.ts b/src/components/security-code-error/security-code-error-controller.ts index 40ac2df73d..d15791f750 100644 --- a/src/components/security-code-error/security-code-error-controller.ts +++ b/src/components/security-code-error/security-code-error-controller.ts @@ -59,7 +59,6 @@ export function securityCodeTriesExceededGet( ), isResendCodeRequest: req.query.isResendCodeRequest, isAccountCreationJourney: req.session.user?.isAccountCreationJourney, - strategicAppChannel: res.locals.strategicAppChannel, }); } diff --git a/src/components/security-code-error/tests/security-code-error-controller.test.ts b/src/components/security-code-error/tests/security-code-error-controller.test.ts index c579857d3e..5c74784972 100644 --- a/src/components/security-code-error/tests/security-code-error-controller.test.ts +++ b/src/components/security-code-error/tests/security-code-error-controller.test.ts @@ -77,8 +77,6 @@ describe("security code controller", () => { newCodeLink: params.newCodeLink, isResendCodeRequest: undefined, isAccountCreationJourney: params.isAccountCreationJourney, - - strategicAppChannel: res.locals.strategicAppChannel, } ); }); @@ -374,8 +372,6 @@ describe("security code controller", () => { ), isResendCodeRequest: undefined, isAccountCreationJourney: undefined, - - strategicAppChannel: res.locals.strategicAppChannel, } ); expect(req.session.user.codeRequestLock).to.eq( @@ -390,7 +386,6 @@ describe("security code controller", () => { () => { req.query.actionType = SecurityCodeErrorType.MfaMaxRetries; req.session.user.isPasswordResetJourney = true; - res.locals.strategicAppChannel = true; securityCodeTriesExceededGet(req as Request, res as Response); @@ -404,8 +399,6 @@ describe("security code controller", () => { ), isResendCodeRequest: undefined, isAccountCreationJourney: undefined, - - strategicAppChannel: res.locals.strategicAppChannel, } ); } @@ -417,7 +410,6 @@ describe("security code controller", () => { () => { req.query.actionType = SecurityCodeErrorType.OtpBlocked; req.session.user.isAccountRecoveryJourney = true; - res.locals.strategicAppChannel = true; securityCodeTriesExceededGet(req as Request, res as Response); expect(res.render).to.have.calledWith( @@ -426,8 +418,6 @@ describe("security code controller", () => { newCodeLink: getNewCodePath(SecurityCodeErrorType.OtpBlocked), isResendCodeRequest: undefined, isAccountCreationJourney: undefined, - - strategicAppChannel: res.locals.strategicAppChannel, } ); } @@ -436,7 +426,6 @@ describe("security code controller", () => { it("should render index-too-many-requests.njk for MfaMaxRetries when max number of codes have been sent and user is in the account creation journey", () => { req.query.actionType = SecurityCodeErrorType.MfaMaxRetries; req.session.user.isAccountCreationJourney = true; - res.locals.strategicAppChannel = true; securityCodeTriesExceededGet(req as Request, res as Response); expect(res.render).to.have.calledWith( @@ -449,8 +438,6 @@ describe("security code controller", () => { ), isResendCodeRequest: undefined, isAccountCreationJourney: true, - - strategicAppChannel: res.locals.strategicAppChannel, } ); }); From 1dbfefe2e062650d5dee1333aa500bd1258dc129 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Wed, 21 May 2025 15:05:36 +0100 Subject: [PATCH 08/11] AUT-4297: Add genericApp channel to index-sign-in-retry-blocked - res.locals.isApp is set to true if the channel is generic_app or strategic_app --- src/components/enter-password/index-sign-in-retry-blocked.njk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/enter-password/index-sign-in-retry-blocked.njk b/src/components/enter-password/index-sign-in-retry-blocked.njk index a95a534596..12090dd190 100644 --- a/src/components/enter-password/index-sign-in-retry-blocked.njk +++ b/src/components/enter-password/index-sign-in-retry-blocked.njk @@ -16,7 +16,7 @@

    {{'pages.signInRetryBlocked.paragraph' | translate}}

    {{'pages.signInRetryBlocked.subHeader' | translate}}

    -{% if strategicAppChannel === true %} +{% if isApp %}

    {{ 'mobileAppPages.signInRetryBlocked.whatCanYouDo.resetYourPassword' | translate }} .
    From 1eb89b1852bd804abbf97ebb10c5f7dafff367c9 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Thu, 22 May 2025 06:55:58 +0100 Subject: [PATCH 09/11] AUT-4297: Change names to reflect new app grouping --- src/utils/get-channel-specific-error-message.ts | 4 ++-- test/unit/utils/get-channel-specific-error-message.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/get-channel-specific-error-message.ts b/src/utils/get-channel-specific-error-message.ts index b773e78d9f..fe6a5905a7 100644 --- a/src/utils/get-channel-specific-error-message.ts +++ b/src/utils/get-channel-specific-error-message.ts @@ -1,10 +1,10 @@ import { logger } from "./logger.js"; export function getChannelSpecificErrorMessage( webMessage: string, - isStrategicAppChannel: boolean, + isAnAppChannel: boolean, messageMappings: Record ): string { - if (!isStrategicAppChannel) { + if (!isAnAppChannel) { return webMessage; } diff --git a/test/unit/utils/get-channel-specific-error-message.test.ts b/test/unit/utils/get-channel-specific-error-message.test.ts index 470246d9fb..26cd560110 100644 --- a/test/unit/utils/get-channel-specific-error-message.test.ts +++ b/test/unit/utils/get-channel-specific-error-message.test.ts @@ -6,7 +6,7 @@ const mappings = { }; describe("getChannelSpecificErrorMessage", () => { - describe("where the channel is not mobile", () => { + describe("where the channel is not an app", () => { describe("and the webMessage is not mapped", () => { it("should return the original webMessage", () => { expect( @@ -31,7 +31,7 @@ describe("getChannelSpecificErrorMessage", () => { }); }); - describe("where the channel is mobile", () => { + describe("where the channel is an app", () => { describe("and the webMessage is not mapped", () => { it("should return the original webMessage", () => { expect( From 7951900aa779097be00290e345312fc760bfa755 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Thu, 22 May 2025 07:01:19 +0100 Subject: [PATCH 10/11] AUT-4297: Change security-code-error templates to ref isApp --- .../index-security-code-entered-exceeded.njk | 2 +- src/components/security-code-error/index-wait.njk | 2 +- src/components/security-code-error/index.njk | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/security-code-error/index-security-code-entered-exceeded.njk b/src/components/security-code-error/index-security-code-entered-exceeded.njk index c717a954b5..66251e4f8b 100644 --- a/src/components/security-code-error/index-security-code-entered-exceeded.njk +++ b/src/components/security-code-error/index-security-code-entered-exceeded.njk @@ -19,7 +19,7 @@ {{'pages.securityCodeInvalid.2hLockout.signInJourney.info.paragraph2' | translate}} - {% if strategicAppChannel === true %} + {% if isApp %}

    {{'mobileAppPages.securityCodeInvalid.2hLockout.signInJourney.info.paragraph3' | translate}}

    {% else %}

    {{'pages.securityCodeInvalid.2hLockout.signInJourney.info.paragraph3' | translate}}

    diff --git a/src/components/security-code-error/index-wait.njk b/src/components/security-code-error/index-wait.njk index 208dd4e4ee..f3bf7dc162 100644 --- a/src/components/security-code-error/index-wait.njk +++ b/src/components/security-code-error/index-wait.njk @@ -17,7 +17,7 @@ {% endif %}

    {{'pages.securityCodeWaitToRequest.info.subHeading' | translate}}

    - {% if strategicAppChannel === true %} + {% if isApp %}

    {{'mobileAppPages.securityCodeWaitToRequest.info.paragraph2' | translate}}

    {% else %}

    {{'pages.securityCodeWaitToRequest.info.paragraph2' | translate}}

    diff --git a/src/components/security-code-error/index.njk b/src/components/security-code-error/index.njk index a9f3b4fc38..3a7d3b17aa 100644 --- a/src/components/security-code-error/index.njk +++ b/src/components/security-code-error/index.njk @@ -11,7 +11,7 @@

    {{'pages.securityCodeInvalid.2hLockout.signInJourney.info.paragraph2' | translate}}

    - {% if strategicAppChannel === true %} + {% if isApp %}

    {{'mobileAppPages.securityCodeInvalid.2hLockout.signInJourney.info.paragraph3' | translate}}

    {% else %}

    {{'pages.securityCodeInvalid.2hLockout.signInJourney.info.paragraph3' | translate}}

    From 5c0c5b78a9a7061c00b962705dcc3cf60e393287 Mon Sep 17 00:00:00 2001 From: Aidan Comer Date: Thu, 22 May 2025 07:55:13 +0100 Subject: [PATCH 11/11] BAU: Fix imports for test file - The tests weren't able to run locally without the imports despite running successfully in GHA - Also deleted unnecessary res.locals.strategicAppChannel assignment --- src/handlers/tests/internal-server-error-handler.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/handlers/tests/internal-server-error-handler.test.ts b/src/handlers/tests/internal-server-error-handler.test.ts index 3975d08d25..d22dee379b 100644 --- a/src/handlers/tests/internal-server-error-handler.test.ts +++ b/src/handlers/tests/internal-server-error-handler.test.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; +import { describe } from "mocha"; import type { NextFunction, Request, Response } from "express"; -import sinon from "sinon"; +import { sinon } from "../../../test/utils/test-utils.js"; import { ERROR_MESSAGES, HTTP_STATUS_CODES } from "../../app.constants.js"; import { serverErrorHandler } from "../internal-server-error-handler.js"; describe("serverErrorHandler", () => { @@ -17,9 +18,6 @@ describe("serverErrorHandler", () => { status: function (newStatus: number) { this.statusCode = newStatus; }, - locals: { - strategicAppChannel: false, - }, } as unknown as Response; next = sinon.spy(); });