From da969cdf7f04fb126a8764426978dc5b4c8a50a3 Mon Sep 17 00:00:00 2001 From: Brian Hurst Date: Wed, 30 Oct 2024 13:28:18 -0400 Subject: [PATCH] Add nonce to headers to be used in components --- src/components/ProgressBar.tsx | 5 ++++- src/middleware.ts | 3 ++- src/middlewares/withCSP.ts | 33 ++++++++++++++++++--------------- src/middlewares/withNonce.ts | 11 +++++++++++ 4 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 src/middlewares/withNonce.ts diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index 9c6f8aef..5451fa6a 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -1,6 +1,7 @@ +import { headers } from 'next/headers'; import { InfinitySVG } from '@/components/svgs/InfinitySVG'; -export function ProgressBar({ +export async function ProgressBar({ total, // number representing total length of bar fill, // number representing how much bar should be filled threshold1 = 75, // percentage where color should change first, between 0 and 100 @@ -13,6 +14,7 @@ export function ProgressBar({ threshold2?: number; changeColors?: boolean; }) { + const nonce = (await headers()).get('x-nonce') || undefined; const heightClass = 'height-1'; const percentage = total ? Math.floor((fill / total) * 100) : 100; let color = 'bg-mint'; @@ -33,6 +35,7 @@ export function ProgressBar({ className={`${heightClass} radius-pill ${color}`} style={{ width: `${percentage}%` }} data-testid="progress" + nonce={nonce} > {!total && ( diff --git a/src/middleware.ts b/src/middleware.ts index 9a8b9889..7a004d7b 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,8 +1,9 @@ import { stackMiddlewares } from './middlewares/stackMiddlewares'; import { withAuth } from './middlewares/withAuth'; +import { withNonce } from './middlewares/withNonce'; import { withCSP } from './middlewares/withCSP'; -export default stackMiddlewares([withCSP, withAuth]); +export default stackMiddlewares([withNonce, withCSP, withAuth]); export const config = { matcher: [ diff --git a/src/middlewares/withCSP.ts b/src/middlewares/withCSP.ts index ec0d6919..98e40dba 100644 --- a/src/middlewares/withCSP.ts +++ b/src/middlewares/withCSP.ts @@ -4,9 +4,9 @@ import { MiddlewareFactory } from './types'; export const withCSP: MiddlewareFactory = (next: NextMiddleware) => { return async (request: NextRequest, _next: NextFetchEvent) => { - const nonce = Buffer.from(crypto.randomUUID()).toString('base64'); - - const cspHeader = ` + if (request.headers.get('x-nonce') != null) { + const nonce = request.headers.get('x-nonce'); + const cspHeader = ` default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${process.env.NODE_ENV === 'development' ? "'unsafe-eval'" : ''}; style-src 'self' 'nonce-${nonce}'; @@ -19,21 +19,24 @@ export const withCSP: MiddlewareFactory = (next: NextMiddleware) => { ${process.env.NODE_ENV !== 'development' ? 'upgrade-insecure-requests;' : ''}; `; - // Replace newline characters and spaces - const contentSecurityPolicyHeaderValue = cspHeader - .replace(/\s{2,}/g, ' ') - .trim(); - request.headers.set( - 'Content-Security-Policy', - contentSecurityPolicyHeaderValue - ); - const response = await next(request, _next); - if (response) { - response.headers.set( + // Replace newline characters and spaces + const contentSecurityPolicyHeaderValue = cspHeader + .replace(/\s{2,}/g, ' ') + .trim(); + request.headers.set( 'Content-Security-Policy', contentSecurityPolicyHeaderValue ); + const response = await next(request, _next); + if (response) { + response.headers.set( + 'Content-Security-Policy', + contentSecurityPolicyHeaderValue + ); + } + return response; + } else { + return next(request, _next); } - return response; }; }; diff --git a/src/middlewares/withNonce.ts b/src/middlewares/withNonce.ts new file mode 100644 index 00000000..f9e0b459 --- /dev/null +++ b/src/middlewares/withNonce.ts @@ -0,0 +1,11 @@ +import { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server'; + +import { MiddlewareFactory } from './types'; + +export const withNonce: MiddlewareFactory = (next: NextMiddleware) => { + return async (request: NextRequest, _next: NextFetchEvent) => { + const nonce = Buffer.from(crypto.randomUUID()).toString('base64'); + request.headers.set('x-nonce', nonce); + return next(request, _next); + }; +};