diff --git a/ci/terraform/ecs.tf b/ci/terraform/ecs.tf index 033de4a22..28a4705cf 100644 --- a/ci/terraform/ecs.tf +++ b/ci/terraform/ecs.tf @@ -168,6 +168,10 @@ locals { name = "LANGUAGE_TOGGLE_ENABLED" value = var.language_toggle_enabled }, + { + name = "PROVE_IDENTITY_WELCOME_ENABLED" + value = var.prove_identity_welcome_enabled + }, { name = "GA4_DISABLED" value = var.ga4_disabled diff --git a/ci/terraform/variables.tf b/ci/terraform/variables.tf index 834bb1421..af0f57b4a 100644 --- a/ci/terraform/variables.tf +++ b/ci/terraform/variables.tf @@ -253,6 +253,12 @@ variable "support_check_email_fraud" { default = "0" } +variable "prove_identity_welcome_enabled" { + description = "Do not show the prove identity welcome screen when disabled" + type = string + default = "1" +} + variable "email_entered_wrong_blocked_minutes" { description = "The duration, in minutes, for which a user is blocked after entering the wrong email multiple times during reauthentication" default = "15" @@ -285,7 +291,7 @@ variable "rate_limited_endpoints_requests_per_period" { default = 100000 } -#cloudfront variable +#cloudfront variable variable "cloudfront_auth_frontend_enabled" { type = bool default = false @@ -340,7 +346,7 @@ variable "cloudfront_WafAcl_Logdestination" { default = "none" description = "CSLS logging destinatiin for logging Cloufront CloakingOriginWebACL WAf logs " } -#end of cloudfront variable +#end of cloudfront variable variable "language_toggle_enabled" { type = string @@ -376,4 +382,4 @@ variable "analytics_cookie_domain" { type = string default = "" description = "Analytics cookie domain where cookie is set" -} +} \ No newline at end of file diff --git a/src/components/authorize/authorize-controller.ts b/src/components/authorize/authorize-controller.ts index 0be661248..0784136bb 100644 --- a/src/components/authorize/authorize-controller.ts +++ b/src/components/authorize/authorize-controller.ts @@ -24,6 +24,7 @@ import { appendQueryParamIfHasValue } from "../../utils/url"; import { getOrchToAuthExpectedClientId, supportReauthentication, + proveIdentityWelcomeEnabled, } from "../../config"; import { logger } from "../../utils/logger"; import { Claims } from "./claims-config"; @@ -137,6 +138,7 @@ export function authorizeGet( skipAuthentication: req.session.user.docCheckingAppUser, mfaMethodType: startAuthResponse.data.user.mfaMethodType, isReauthenticationRequired: req.session.user.reauthenticate, + proveIdentityWelcomeEnabled: proveIdentityWelcomeEnabled(), }, sessionId ); diff --git a/src/components/authorize/tests/authorize-controller.test.ts b/src/components/authorize/tests/authorize-controller.test.ts index da5541737..4f5ce93e9 100644 --- a/src/components/authorize/tests/authorize-controller.test.ts +++ b/src/components/authorize/tests/authorize-controller.test.ts @@ -177,25 +177,46 @@ describe("authorize controller", () => { expect(res.redirect).to.have.calledWith(PATH_NAMES.AUTH_CODE); }); + it("should redirect to /identity page when identity check required and prove identity welcome is enabled", async () => { + process.env.PROVE_IDENTITY_WELCOME_ENABLED = "1"; + it("should redirect to /identity page when identity check required", async () => { + authServiceResponseData.data.user = { + identityRequired: true, + upliftRequired: false, + authenticated: true, + }; + fakeAuthorizeService = mockAuthService(authServiceResponseData); - it("should redirect to /identity page when identity check required", async () => { - authServiceResponseData.data.user = { - identityRequired: true, - upliftRequired: false, - authenticated: true, - }; - fakeAuthorizeService = mockAuthService(authServiceResponseData); + await authorizeGet( + fakeAuthorizeService, + fakeCookieConsentService, + fakeKmsDecryptionService, + fakeJwtService + )(req as Request, res as Response); - await authorizeGet( - fakeAuthorizeService, - fakeCookieConsentService, - fakeKmsDecryptionService, - fakeJwtService - )(req as Request, res as Response); + expect(res.redirect).to.have.calledWith( + PATH_NAMES.PROVE_IDENTITY_WELCOME + ); + }); - expect(res.redirect).to.have.calledWith( - PATH_NAMES.PROVE_IDENTITY_WELCOME - ); + it("should redirect to /sign-in-or-create page when identity check required and prove identity welcome is not enabled", async () => { + process.env.PROVE_IDENTITY_WELCOME_ENABLED = "0"; + authServiceResponseData.data.user = { + identityRequired: true, + upliftRequired: false, + authenticated: false, + }; + fakeAuthorizeService = mockAuthService(authServiceResponseData); + + await authorizeGet( + fakeAuthorizeService, + fakeCookieConsentService, + fakeKmsDecryptionService, + fakeJwtService + )(req as Request, res as Response); + + expect(res.redirect).to.have.calledWith(PATH_NAMES.SIGN_IN_OR_CREATE); + }); }); it("should redirect to sign in when reauthentication is requested and user is not authenticated and support reauthenticate feature flag is on", async () => { diff --git a/src/components/common/state-machine/state-machine.ts b/src/components/common/state-machine/state-machine.ts index f3145c203..f94a50629 100644 --- a/src/components/common/state-machine/state-machine.ts +++ b/src/components/common/state-machine/state-machine.ts @@ -4,6 +4,7 @@ import { OIDC_PROMPT, PATH_NAMES, } from "../../../app.constants"; +import { proveIdentityWelcomeEnabled } from "../../../config"; const USER_JOURNEY_EVENTS = { AUTHENTICATED: "AUTHENTICATED", @@ -78,6 +79,7 @@ const authStateMachine = createMachine( requiresResetPasswordMFASmsCode: false, requiresResetPasswordMFAAuthAppCode: false, isOnForcedPasswordResetJourney: false, + proveIdentityWelcomeEnabled: proveIdentityWelcomeEnabled(), }, states: { [PATH_NAMES.ROOT]: { @@ -92,7 +94,7 @@ const authStateMachine = createMachine( [USER_JOURNEY_EVENTS.EXISTING_SESSION]: [ { target: [PATH_NAMES.PROVE_IDENTITY_WELCOME], - cond: "isIdentityRequired", + cond: "isIdentityRequiredAndProveIdentityWelcomeEnabled", }, { target: [PATH_NAMES.ENTER_PASSWORD], cond: "requiresLogin" }, { @@ -104,6 +106,10 @@ const authStateMachine = createMachine( target: [PATH_NAMES.ENTER_EMAIL_SIGN_IN], cond: "isReauthenticationRequired", }, + { + target: [PATH_NAMES.PROVE_IDENTITY], + cond: "isIdentityRequired", + }, { target: [PATH_NAMES.AUTH_CODE], cond: "isAuthenticated" }, ], [USER_JOURNEY_EVENTS.NO_EXISTING_SESSION]: [ @@ -117,7 +123,7 @@ const authStateMachine = createMachine( }, { target: [PATH_NAMES.PROVE_IDENTITY_WELCOME], - cond: "isIdentityRequired", + cond: "isIdentityRequiredAndProveIdentityWelcomeEnabled", }, { target: [PATH_NAMES.SIGN_IN_OR_CREATE] }, ], @@ -742,6 +748,9 @@ const authStateMachine = createMachine( skipAuthentication: (context) => context.skipAuthentication === true && context.isAuthenticated === false, + isIdentityRequiredAndProveIdentityWelcomeEnabled: (context) => + context.isIdentityRequired === true && + context.proveIdentityWelcomeEnabled === true, requiresMFAAuthAppCode: (context) => context.mfaMethodType === MFA_METHOD_TYPE.AUTH_APP && context.requiresTwoFactorAuth === true, diff --git a/src/components/common/state-machine/tests/state-machine.test.ts b/src/components/common/state-machine/tests/state-machine.test.ts index 2cca8cdd4..fdf30f62d 100644 --- a/src/components/common/state-machine/tests/state-machine.test.ts +++ b/src/components/common/state-machine/tests/state-machine.test.ts @@ -4,6 +4,99 @@ import { getNextState, USER_JOURNEY_EVENTS } from "../state-machine"; import { MFA_METHOD_TYPE, PATH_NAMES } from "../../../../app.constants"; describe("state-machine", () => { + describe(`getNextState - ${PATH_NAMES.AUTHORIZE} on ${USER_JOURNEY_EVENTS.EXISTING_SESSION}`, () => { + describe("where isIdentityRequired", () => { + it(`should move from ${PATH_NAMES.AUTHORIZE} to ${PATH_NAMES.PROVE_IDENTITY_WELCOME}`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.EXISTING_SESSION, + { + isIdentityRequired: true, + proveIdentityWelcomeEnabled: true, + } + ); + + expect(nextState.value).to.equal(PATH_NAMES.PROVE_IDENTITY_WELCOME); + }); + + it(`should move from ${PATH_NAMES.AUTHORIZE} to ${PATH_NAMES.PROVE_IDENTITY}`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.EXISTING_SESSION, + { + isIdentityRequired: true, + proveIdentityWelcomeEnabled: false, + } + ); + + expect(nextState.value).to.equal(PATH_NAMES.PROVE_IDENTITY); + }); + it(`should stay on ${PATH_NAMES.AUTHORIZE}`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.EXISTING_SESSION, + { isIdentityRequired: false, proveIdentityWelcomeEnabled: false } + ); + expect(nextState.value).to.equal(PATH_NAMES.AUTHORIZE); + }); + it(`should move from ${PATH_NAMES.AUTHORIZE} to ${PATH_NAMES.ENTER_EMAIL_SIGN_IN}`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.EXISTING_SESSION, + { + isIdentityRequired: true, + proveIdentityWelcomeEnabled: false, + isAuthenticated: true, + isReauthenticationRequired: true, + } + ); + expect(nextState.value).to.equal(PATH_NAMES.ENTER_EMAIL_SIGN_IN); + }); + it(`should move from ${PATH_NAMES.AUTHORIZE} to ${PATH_NAMES.PROVE_IDENTITY}`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.EXISTING_SESSION, + { + isIdentityRequired: true, + isAuthenticated: true, + isReauthenticationRequired: false, + proveIdentityWelcomeEnabled: false, + } + ); + expect(nextState.value).to.equal(PATH_NAMES.PROVE_IDENTITY); + }); + }); + }); + + describe(`getNextState - ${PATH_NAMES.AUTHORIZE} on ${USER_JOURNEY_EVENTS.NO_EXISTING_SESSION}`, () => { + describe("where isIdentityRequired", () => { + it(`should move from ${PATH_NAMES.AUTHORIZE} to ${PATH_NAMES.PROVE_IDENTITY_WELCOME} proveIdentityWelcomeEnabled is true`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.NO_EXISTING_SESSION, + { + isIdentityRequired: true, + proveIdentityWelcomeEnabled: true, + } + ); + + expect(nextState.value).to.equal(PATH_NAMES.PROVE_IDENTITY_WELCOME); + }); + + it(`should move from ${PATH_NAMES.AUTHORIZE} to ${PATH_NAMES.SIGN_IN_OR_CREATE} proveIdentityWelcomeEnabled is false`, () => { + const nextState = getNextState( + PATH_NAMES.AUTHORIZE, + USER_JOURNEY_EVENTS.NO_EXISTING_SESSION, + { + isIdentityRequired: true, + proveIdentityWelcomeEnabled: false, + } + ); + + expect(nextState.value).to.equal(PATH_NAMES.SIGN_IN_OR_CREATE); + }); + }); + }); describe("getNextState - login journey (2fa)", () => { it("should move from initial state to sign or create when user event is landing", () => { const nextState = getNextState(PATH_NAMES.ROOT, USER_JOURNEY_EVENTS.ROOT); @@ -43,6 +136,7 @@ describe("state-machine", () => { expect(nextState.value).to.equal(PATH_NAMES.AUTH_CODE); }); }); + describe("getNextState - login journey (non 2fa)", () => { it("should move from initial state to sign or create when user event is landing", () => { const nextState = getNextState(PATH_NAMES.ROOT, USER_JOURNEY_EVENTS.ROOT); diff --git a/src/components/prove-identity-welcome/prove-identity-welcome-controller.ts b/src/components/prove-identity-welcome/prove-identity-welcome-controller.ts index da150cffc..6e80a7a0f 100644 --- a/src/components/prove-identity-welcome/prove-identity-welcome-controller.ts +++ b/src/components/prove-identity-welcome/prove-identity-welcome-controller.ts @@ -2,13 +2,17 @@ import { Request, Response } from "express"; import { getNextPathAndUpdateJourney } from "../common/constants"; import { USER_JOURNEY_EVENTS } from "../common/state-machine/state-machine"; import { PATH_NAMES } from "../../app.constants"; - +import { proveIdentityWelcomeEnabled } from "../../config"; export function proveIdentityWelcomeGet(req: Request, res: Response): void { - res.render( - req.session.user.isAuthenticated - ? "prove-identity-welcome/index-existing-session.njk" - : "prove-identity-welcome/index.njk" - ); + if (!proveIdentityWelcomeEnabled()) { + res.redirect("/sign-in-or-create"); + } else { + res.render( + req.session.user.isAuthenticated + ? "prove-identity-welcome/index-existing-session.njk" + : "prove-identity-welcome/index.njk" + ); + } } export async function proveIdentityWelcomePost( diff --git a/src/components/prove-identity-welcome/tests/prove-identity-welcome-controller.test.ts b/src/components/prove-identity-welcome/tests/prove-identity-welcome-controller.test.ts index 2c3ab6f8d..6fd3b12f8 100644 --- a/src/components/prove-identity-welcome/tests/prove-identity-welcome-controller.test.ts +++ b/src/components/prove-identity-welcome/tests/prove-identity-welcome-controller.test.ts @@ -32,24 +32,31 @@ describe("prove your identity welcome controller", () => { }); describe("proveIdentityWelcomeGet", () => { - it("should render prove your identity welcome page", async () => { + it("should render prove your identity welcome page when page enabled", async () => { + process.env.PROVE_IDENTITY_WELCOME_ENABLED = "1"; proveIdentityWelcomeGet(req as Request, res as Response); - expect(res.render).to.have.been.calledWith( "prove-identity-welcome/index.njk" ); }); - it("should render prove identity welcome page for user that already has an active session", async () => { - req.session.user.isAuthenticated = true; + it("should redirect to sign-in-or-create from prove your identity welcome page when not enabled", async () => { + process.env.PROVE_IDENTITY_WELCOME_ENABLED = "0"; proveIdentityWelcomeGet(req as Request, res as Response); + expect(res.redirect).to.have.been.calledWith( + PATH_NAMES.SIGN_IN_OR_CREATE + ); + }); + it("should render prove identity welcome page for user that already has an active session if page enabled", async () => { + req.session.user.isAuthenticated = true; + process.env.PROVE_IDENTITY_WELCOME_ENABLED = "1"; + proveIdentityWelcomeGet(req as Request, res as Response); expect(res.render).to.have.been.calledWith( "prove-identity-welcome/index-existing-session.njk" ); }); }); - describe("proveIdentityWelcomePost", () => { it("should redirect to sign in or create when user not authenticated", async () => { await proveIdentityWelcomePost(req as Request, res as Response); diff --git a/src/config.ts b/src/config.ts index dcb4868ab..f389759e8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -206,3 +206,6 @@ export function googleAnalytics4Disabled(): string { export function universalAnalyticsDisabled(): string { return process.env.UA_DISABLED || "false"; } +export function proveIdentityWelcomeEnabled(): boolean { + return process.env.PROVE_IDENTITY_WELCOME_ENABLED === "1"; +} diff --git a/yarn.lock b/yarn.lock index 45127d43b..e5e8f5a45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2626,20 +2626,13 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@4, debug@4.3.4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decache@^4.6.1: version "4.6.1" resolved "https://registry.npmjs.org/decache/-/decache-4.6.1.tgz"