@@ -2,29 +2,84 @@ import { NextResponse, type NextRequest } from 'next/server';
22import { getAccessTokenServer } from '@utils/amplify-utils' ;
33import { getBasePath } from '@utils/get-base-path' ;
44
5- function isExcludedPath ( path : string , excludedPaths : string [ ] ) : boolean {
6- return excludedPaths . some ( ( excludedPath ) => path . startsWith ( excludedPath ) ) ;
5+ function getContentSecurityPolicy ( nonce : string ) {
6+ const contentSecurityPolicyDirective = {
7+ 'base-uri' : [ `'self'` ] ,
8+ 'default-src' : [ `'none'` ] ,
9+ 'frame-ancestors' : [ `'none'` ] ,
10+ 'font-src' : [ `'self'` , 'https://assets.nhs.uk' ] ,
11+ 'form-action' : [ `'self'` ] ,
12+ 'frame-src' : [ `'self'` ] ,
13+ 'connect-src' : [ `'self'` , 'https://cognito-idp.eu-west-2.amazonaws.com' ] ,
14+ 'img-src' : [ `'self'` ] ,
15+ 'manifest-src' : [ `'self'` ] ,
16+ 'object-src' : [ `'none'` ] ,
17+ 'script-src' : [ `'self'` , `'nonce-${ nonce } '` ] ,
18+ 'style-src' : [ `'self'` , `'nonce-${ nonce } '` ] ,
19+ 'upgrade-insecure-requests;' : [ ] ,
20+ } ;
21+
22+ if ( process . env . NODE_ENV === 'development' ) {
23+ contentSecurityPolicyDirective [ 'script-src' ] . push ( `'unsafe-eval'` ) ;
24+ }
25+
26+ return Object . entries ( contentSecurityPolicyDirective )
27+ . map ( ( [ key , value ] ) => `${ key } ${ value . join ( ' ' ) } ` )
28+ . join ( '; ' ) ;
29+ }
30+
31+ function isPublicPath ( path : string , publicPaths : string [ ] ) : boolean {
32+ return publicPaths . some ( ( publicPath ) => path . startsWith ( publicPath ) ) ;
733}
834
935export async function middleware ( request : NextRequest ) {
10- const excludedPaths = [ '/create-and-submit-templates' , '/auth' ] ;
36+ const nonce = Buffer . from ( crypto . randomUUID ( ) ) . toString ( 'base64' ) ;
37+
38+ const csp = getContentSecurityPolicy ( nonce ) ;
39+
40+ const requestHeaders = new Headers ( request . headers ) ;
41+ requestHeaders . set ( 'Content-Security-Policy' , csp ) ;
42+
43+ const publicPaths = [ '/create-and-submit-templates' , '/auth' ] ;
1144
12- if ( isExcludedPath ( request . nextUrl . pathname , excludedPaths ) ) {
13- return NextResponse . next ( ) ;
45+ if ( isPublicPath ( request . nextUrl . pathname , publicPaths ) ) {
46+ const publicPathResponse = NextResponse . next ( {
47+ request : {
48+ headers : requestHeaders ,
49+ } ,
50+ } ) ;
51+
52+ publicPathResponse . headers . set ( 'Content-Security-Policy' , csp ) ;
53+
54+ return publicPathResponse ;
1455 }
1556
1657 const token = await getAccessTokenServer ( ) ;
1758
1859 if ( ! token ) {
19- return Response . redirect (
60+ const redirectResponse = NextResponse . redirect (
2061 new URL (
2162 `/auth?redirect=${ encodeURIComponent (
2263 `${ getBasePath ( ) } /${ request . nextUrl . pathname } `
2364 ) } `,
2465 request . url
2566 )
2667 ) ;
68+
69+ redirectResponse . headers . set ( 'Content-Type' , 'text/html' ) ;
70+
71+ return redirectResponse ;
2772 }
73+
74+ const response = NextResponse . next ( {
75+ request : {
76+ headers : requestHeaders ,
77+ } ,
78+ } ) ;
79+
80+ response . headers . set ( 'Content-Security-Policy' , csp ) ;
81+
82+ return response ;
2883}
2984
3085export const config = {
@@ -34,7 +89,8 @@ export const config = {
3489 * - _next/static (static files)
3590 * - _next/image (image optimization files)
3691 * - favicon.ico (favicon file)
92+ * - lib/ (our static content)
3793 */
38- '/((?!_next/static|_next/image|favicon.ico).*)' ,
94+ '/((?!_next/static|_next/image|favicon.ico|lib/ ).*)' ,
3995 ] ,
4096} ;
0 commit comments