diff --git a/frontend/next.config.js b/frontend/next.config.js index d6727a938..feaf6cf72 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -4,7 +4,6 @@ const { PHASE_DEVELOPMENT_SERVER } = require('next/constants'); const amplifyConfig = require('./amplify_outputs.json'); const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? '/templates'; -const domain = process.env.NOTIFY_DOMAIN_NAME ?? 'localhost:3000'; const nextConfig = (phase) => { const isDevServer = phase === PHASE_DEVELOPMENT_SERVER; @@ -37,42 +36,10 @@ const nextConfig = (phase) => { basePath: false, permanent: false, }, - { - source: `${basePath}/auth/inactive`, - destination: '/auth/inactive', - permanent: false, - basePath: false, - }, - { - source: `${basePath}/auth/signout`, - destination: '/auth/signout', - basePath: false, - permanent: false, - }, ]; }, async rewrites() { - if (includeAuthPages) { - return [ - { - source: '/auth/inactive', - destination: `http://${domain}${basePath}/auth/idle`, - basePath: false, - }, - { - source: '/auth/signout', - destination: `http://${domain}${basePath}/auth/signout`, - basePath: false, - }, - { - source: '/auth', - destination: `http://${domain}${basePath}/auth`, - basePath: false, - }, - ]; - } - return []; }, diff --git a/frontend/src/__tests__/components/molecules/LogoutWarningModal/__snapshots__/LogoutWarningModal.test.tsx.snap b/frontend/src/__tests__/components/molecules/LogoutWarningModal/__snapshots__/LogoutWarningModal.test.tsx.snap index 1ea669db1..1f7c08313 100644 --- a/frontend/src/__tests__/components/molecules/LogoutWarningModal/__snapshots__/LogoutWarningModal.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/LogoutWarningModal/__snapshots__/LogoutWarningModal.test.tsx.snap @@ -43,7 +43,7 @@ exports[`LogoutWarningModal should match snapshot 1`] = ` Sign out diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/AuthLink.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/AuthLink.test.tsx.snap index 21d22dc52..03cd61a47 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/AuthLink.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/AuthLink.test.tsx.snap @@ -5,7 +5,7 @@ exports[`AuthLink renders Sign in link when authStatus changes to unauthenticate Sign in @@ -18,7 +18,7 @@ exports[`AuthLink renders Sign in link when authStatus is configuring 1`] = ` Sign in @@ -31,7 +31,7 @@ exports[`AuthLink renders Sign out link when authStatus changes to authenticated Sign out diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/Header.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/Header.test.tsx.snap index 0c62d87a4..deb18d42c 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/Header.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/Header.test.tsx.snap @@ -82,7 +82,7 @@ exports[`NhsNotifyHeader when authenticated matches snapshot (authenticated) 1`] Sign out @@ -176,7 +176,7 @@ exports[`NhsNotifyHeader when unauthenticated matches snapshot (unauthenticated) Sign in diff --git a/frontend/src/__tests__/utils/get-auth-url.test.ts b/frontend/src/__tests__/utils/get-auth-url.test.ts new file mode 100644 index 000000000..514b52e96 --- /dev/null +++ b/frontend/src/__tests__/utils/get-auth-url.test.ts @@ -0,0 +1,189 @@ +import { getAuthUrl } from '@utils/get-auth-url'; + +describe('getAuthUrl', () => { + const originalWindow = { ...global.window }; + const originalEnv = { ...process.env }; + + afterAll(() => { + Object.defineProperty(process, 'env', { + value: originalEnv, + configurable: true, + }); + + Object.defineProperty(global, 'window', { + value: originalWindow, + configurable: true, + }); + }); + + describe('client side (when window is available)', () => { + beforeEach(() => { + Object.defineProperty(global, 'window', { + value: { + location: { + protocol: 'https:', + host: 'nhsnotify.national.nhs.uk', + }, + }, + configurable: true, + }); + }); + + afterAll(() => { + Object.defineProperty(global, 'window', { + value: originalWindow, + configurable: true, + }); + }); + + it('should construct URL', () => { + const result = getAuthUrl('/auth'); + expect(result).toBe('https://nhsnotify.national.nhs.uk/auth'); + }); + + it('should handle query parameters', () => { + const result = getAuthUrl('/auth?redirect=%2Ftemplates%2Fcreate'); + + expect(result).toBe( + 'https://nhsnotify.national.nhs.uk/auth?redirect=%2Ftemplates%2Fcreate' + ); + }); + + describe('in development env', () => { + beforeEach(() => { + Object.defineProperty(process.env, 'NODE_ENV', { + value: 'development', + configurable: true, + }); + }); + + afterAll(() => { + Object.defineProperty(process.env, 'NODE_ENV', { + value: originalEnv, + configurable: true, + }); + }); + + it('should include the base path set', () => { + process.env.NEXT_PUBLIC_BASE_PATH = '/base-path'; + + const result = getAuthUrl('/auth'); + expect(result).toBe('https://nhsnotify.national.nhs.uk/base-path/auth'); + }); + + it('should fallback to templates when no base path environment variable provided', () => { + delete process.env.NEXT_PUBLIC_BASE_PATH; + + const result = getAuthUrl('/auth'); + expect(result).toBe('https://nhsnotify.national.nhs.uk/templates/auth'); + }); + }); + }); + + describe('when window is not available', () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (global as any).window; + }); + + afterAll(() => { + global.window = originalWindow; + }); + + describe('when gateway URL environment variable is available', () => { + beforeEach(() => { + process.env.NEXT_PUBLIC_GATEWAY_URL = + 'https://dev.web-gateway.nhsnotify.national.nhs.uk'; + }); + + it('should use NEXT_PUBLIC_GATEWAY_URL', () => { + const result = getAuthUrl('/auth'); + expect(result).toBe( + 'https://dev.web-gateway.nhsnotify.national.nhs.uk/auth' + ); + }); + + it('should handle query parameters', () => { + const result = getAuthUrl('/auth?redirect=%2Ftemplates%2Fcreate'); + expect(result).toBe( + 'https://dev.web-gateway.nhsnotify.national.nhs.uk/auth?redirect=%2Ftemplates%2Fcreate' + ); + }); + + describe('in development env', () => { + beforeEach(() => { + Object.defineProperty(process.env, 'NODE_ENV', { + value: 'development', + configurable: true, + }); + }); + + afterAll(() => { + Object.defineProperty(process.env, 'NODE_ENV', { + value: originalEnv, + configurable: true, + }); + }); + + it('should include the base path set', () => { + process.env.NEXT_PUBLIC_BASE_PATH = '/base-path'; + + const result = getAuthUrl('/auth'); + expect(result).toBe( + 'https://dev.web-gateway.nhsnotify.national.nhs.uk/base-path/auth' + ); + }); + + it('should fallback to templates when no base path environment variable provided', () => { + delete process.env.NEXT_PUBLIC_BASE_PATH; + + const result = getAuthUrl('/auth'); + expect(result).toBe( + 'https://dev.web-gateway.nhsnotify.national.nhs.uk/templates/auth' + ); + }); + }); + }); + + describe('when no gateway URL environment variable is available', () => { + beforeEach(() => { + delete process.env.NEXT_PUBLIC_GATEWAY_URL; + }); + + it('should fallback to localhost:3000', () => { + const result = getAuthUrl('/auth'); + expect(result).toBe('http://localhost:3000/auth'); + }); + + describe('in development env', () => { + beforeEach(() => { + Object.defineProperty(process.env, 'NODE_ENV', { + value: 'development', + configurable: true, + }); + }); + + afterAll(() => { + Object.defineProperty(process.env, 'NODE_ENV', { + value: originalEnv, + configurable: true, + }); + }); + + it('should include the base path set', () => { + process.env.NEXT_PUBLIC_BASE_PATH = '/base-path'; + + const result = getAuthUrl('/auth'); + expect(result).toBe('http://localhost:3000/base-path/auth'); + }); + + it('should fallback to templates when no base path environment variable provided', () => { + delete process.env.NEXT_PUBLIC_BASE_PATH; + + const result = getAuthUrl('/auth'); + expect(result).toBe('http://localhost:3000/templates/auth'); + }); + }); + }); + }); +}); diff --git a/frontend/src/app/auth/signin/route.dev.ts b/frontend/src/app/auth/signin/route.dev.ts index 054f2efd6..ac31f700e 100644 --- a/frontend/src/app/auth/signin/route.dev.ts +++ b/frontend/src/app/auth/signin/route.dev.ts @@ -33,10 +33,5 @@ export const GET = async (request: NextRequest) => { } } - return NextResponse.json(null, { - status: 307, - headers: { - Location: redirectPath, - }, - }); + return NextResponse.redirect(redirectPath, { status: 307 }); }; diff --git a/frontend/src/content/content.ts b/frontend/src/content/content.ts index 225da778d..410f4cadd 100644 --- a/frontend/src/content/content.ts +++ b/frontend/src/content/content.ts @@ -1,5 +1,6 @@ import type { ContentBlock } from '@molecules/ContentRenderer/ContentRenderer'; import { getBasePath } from '@utils/get-base-path'; +import { getAuthUrl } from '@utils/get-auth-url'; import { TemplateStatus, TemplateType } from 'nhs-notify-backend-client'; const generatePageTitle = (title: string): string => { @@ -25,13 +26,15 @@ const header = { links: { signIn: { text: 'Sign in', - href: `/auth?redirect=${encodeURIComponent( - `${getBasePath()}/create-and-submit-templates` - )}`, + href: getAuthUrl( + `/auth?redirect=${encodeURIComponent( + `${getBasePath()}/create-and-submit-templates` + )}` + ), }, signOut: { text: 'Sign out', - href: '/auth/signout', + href: getAuthUrl('/auth/signout'), }, }, }, diff --git a/frontend/src/utils/get-auth-url.ts b/frontend/src/utils/get-auth-url.ts new file mode 100644 index 000000000..c5dc69fbf --- /dev/null +++ b/frontend/src/utils/get-auth-url.ts @@ -0,0 +1,20 @@ +function getBasePath(): string { + return process.env.NODE_ENV === 'development' + ? (process.env.NEXT_PUBLIC_BASE_PATH ?? '/templates') + : ''; +} + +export function getAuthUrl(path: string): string { + const basePath = getBasePath(); + + if (typeof window !== 'undefined') { + return `${window.location.protocol}//${window.location.host}${basePath}${path}`; + } + + const gatewayUrl = process.env.NEXT_PUBLIC_GATEWAY_URL; + if (gatewayUrl) { + return `${gatewayUrl}${basePath}${path}`; + } + + return `http://localhost:3000${basePath}${path}`; +} diff --git a/infrastructure/terraform/components/app/amplify_app.tf b/infrastructure/terraform/components/app/amplify_app.tf index 31e186e7b..e4998cc46 100644 --- a/infrastructure/terraform/components/app/amplify_app.tf +++ b/infrastructure/terraform/components/app/amplify_app.tf @@ -32,8 +32,10 @@ resource "aws_amplify_app" "main" { AMPLIFY_MONOREPO_APP_ROOT = "frontend" API_BASE_URL = module.backend_api.api_base_url CSRF_SECRET = aws_ssm_parameter.csrf_secret.value + NEXT_PUBLIC_GATEWAY_URL = local.gateway_url NEXT_PUBLIC_PROMPT_SECONDS_BEFORE_LOGOUT = 120 NEXT_PUBLIC_TIME_TILL_LOGOUT_SECONDS = 900 + NOTIFY_DOMAIN_NAME = local.root_domain_name NOTIFY_ENVIRONMENT = var.environment NOTIFY_GROUP = var.group USER_POOL_CLIENT_ID = jsondecode(aws_ssm_parameter.cognito_config.value)["USER_POOL_CLIENT_ID"] diff --git a/infrastructure/terraform/components/app/locals.tf b/infrastructure/terraform/components/app/locals.tf index e078e5392..65f4f0558 100644 --- a/infrastructure/terraform/components/app/locals.tf +++ b/infrastructure/terraform/components/app/locals.tf @@ -3,4 +3,9 @@ locals { root_domain_name = "${var.environment}.${local.acct.dns_zone["name"]}" lambdas_source_code_dir = "../../../../lambdas" log_destination_arn = "arn:aws:logs:${var.region}:${var.observability_account_id}:destination:nhs-main-obs-firehose-logs" + gateway_url = var.gateway_domain != null ? ( + var.use_environment_specific_gateway_domain + ? "https://${var.environment}.${var.gateway_domain}" + : "https://${var.gateway_domain}" + ) : "https://${aws_amplify_app.main.default_domain}" } diff --git a/infrastructure/terraform/components/app/module_amplify_branch.tf b/infrastructure/terraform/components/app/module_amplify_branch.tf index ceb0e02f8..6e4183152 100644 --- a/infrastructure/terraform/components/app/module_amplify_branch.tf +++ b/infrastructure/terraform/components/app/module_amplify_branch.tf @@ -16,7 +16,8 @@ module "amplify_branch" { enable_auto_build = false environment_variables = { - NOTIFY_SUBDOMAIN = var.environment - NEXT_PUBLIC_BASE_PATH = "/templates" + NOTIFY_SUBDOMAIN = var.environment + NEXT_PUBLIC_BASE_PATH = "/templates" + NEXT_PUBLIC_GATEWAY_URL = local.gateway_url } } diff --git a/infrastructure/terraform/components/app/outputs.tf b/infrastructure/terraform/components/app/outputs.tf index c3af0e6d4..fbebf6ff1 100644 --- a/infrastructure/terraform/components/app/outputs.tf +++ b/infrastructure/terraform/components/app/outputs.tf @@ -3,6 +3,7 @@ output "amplify" { id = aws_amplify_app.main.id domain_name = local.root_domain_name branch_name = var.branch_name + gateway_url = local.gateway_url } } diff --git a/infrastructure/terraform/components/app/variables.tf b/infrastructure/terraform/components/app/variables.tf index 03a3f705a..36bba0da8 100644 --- a/infrastructure/terraform/components/app/variables.tf +++ b/infrastructure/terraform/components/app/variables.tf @@ -212,3 +212,15 @@ variable "external_email_domain" { default = null description = "Externally managed domain used to create an SES identity for sending emails from. Validation DNS records will need to be manually configured in the DNS provider." } + +variable "gateway_domain" { + type = string + description = "The web gateway domain (e.g., notify.nhs.uk or web-gateway.nhsnotify.national.nhs.uk)" + default = "" +} + +variable "use_environment_specific_gateway_domain" { + type = bool + description = "Whether to prefix the gateway domain with the environment name" + default = false +} diff --git a/infrastructure/terraform/components/branch/module_amplify_branch.tf b/infrastructure/terraform/components/branch/module_amplify_branch.tf index f9badd863..65ba36841 100644 --- a/infrastructure/terraform/components/branch/module_amplify_branch.tf +++ b/infrastructure/terraform/components/branch/module_amplify_branch.tf @@ -18,7 +18,8 @@ module "amplify_branch" { enable_auto_build = true environment_variables = { - NOTIFY_SUBDOMAIN = var.environment - NEXT_PUBLIC_BASE_PATH = "/templates~${local.normalised_branch_name}" + NOTIFY_SUBDOMAIN = var.environment + NEXT_PUBLIC_BASE_PATH = "/templates~${local.normalised_branch_name}" + NEXT_PUBLIC_GATEWAY_URL = local.app.amplify["gateway_url"] } }