diff --git a/src/__tests__/middleware.test.ts b/src/__tests__/middleware.test.ts new file mode 100644 index 00000000..b0742aba --- /dev/null +++ b/src/__tests__/middleware.test.ts @@ -0,0 +1,19 @@ +/** + * @jest-environment node + */ +import { NextRequest } from 'next/server'; +import { middleware } from '../middleware'; + +const cspRegex = + /^base-uri 'self'; form-action 'self'; frame-ancestors 'none'; default-src 'none'; connect-src 'self'; font-src 'self' https:\/\/assets.nhs.uk; img-src 'self'; script-src 'self' 'nonce-[\dA-Za-z]+'; style-src 'self'; upgrade-insecure-requests$/; + +describe('middleware function', () => { + it('sets CSP in response', () => { + const request = new NextRequest( + new URL('https://main.web-gateway.dev.nhsnotify.national.nhs.uk') + ); + const response = middleware(request); + const csp = response.headers.get('Content-Security-Policy'); + expect(csp).toMatch(cspRegex); + }); +}); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 13799c1c..85516258 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,8 @@ import content from '@/src/content/content'; import { ClientLayout } from '@/src/components/molecules/ClientLayout/ClientLayout'; import 'nhsuk-frontend/dist/nhsuk.css'; +export const dynamic = 'force-dynamic'; + export const metadata: Metadata = { title: content.global.mainLayout.title, description: content.global.mainLayout.description, diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 00000000..38a23b94 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export function middleware(request: NextRequest) { + const nonce = Buffer.from(crypto.randomUUID()).toString('base64'); + + const cspUnsafeEval = + process.env.NODE_ENV === 'production' ? '' : `http: 'unsafe-eval'`; + + const csp = `base-uri 'self'; form-action 'self'; frame-ancestors 'none'; default-src 'none'; connect-src 'self' https://cognito-idp.eu-west-2.amazonaws.com; font-src 'self' https://assets.nhs.uk; img-src 'self'; script-src 'self' 'nonce-${nonce}' ${cspUnsafeEval}; style-src 'self' 'nonce-${nonce}'; upgrade-insecure-requests;`; + + const requestHeaders = new Headers(request.headers); + requestHeaders.set('x-nonce', nonce); + + requestHeaders.set('Content-Security-Policy', csp); + + const response = NextResponse.next({ + request: { + headers: requestHeaders, + }, + }); + response.headers.set('Content-Security-Policy', csp); + + return response; +} + +// export const config = { +// matcher: [ +// /* +// * Match all request paths except for the ones starting with: +// * - _next/static (static files) +// * - _next/image (image optimization files) +// * - favicon.ico (favicon file) +// */ +// '/((?!_next/static|_next/image|favicon.ico).*)', +// ], +// };