From 68f49bcf53ea25cf7c561b20ed053bd1b0bd061a Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Tue, 30 Jun 2026 01:55:00 +0100 Subject: [PATCH 1/8] feat: integrate CSP with report-only mode and nonce support --- FrontEnd/my-app/next.config.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/FrontEnd/my-app/next.config.ts b/FrontEnd/my-app/next.config.ts index d416a63ca..9c8c8e5dd 100644 --- a/FrontEnd/my-app/next.config.ts +++ b/FrontEnd/my-app/next.config.ts @@ -10,7 +10,16 @@ const withAnalyzer = withBundleAnalyzer({ const nextConfig: NextConfig = { outputFileTracingRoot: __dirname, async headers() { - return cspHeaders; + const isDev = process.env.NODE_ENV !== 'production'; + const headerKey = isDev ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; + // Replace the CSP header key based on environment + const modifiedHeaders = cspHeaders.map(h => { + if (h.key === 'Content-Security-Policy') { + return { key: headerKey, value: h.value }; + } + return h; + }); + return [{ source: '/(.*)', headers: modifiedHeaders }]; }, }; From ae7ea7d0af8f434fc5df182f827c085b45d483eb Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Wed, 1 Jul 2026 15:01:58 +0100 Subject: [PATCH 2/8] fix: resolve nested CSP headers structure to fix Playwright web server boot --- FrontEnd/my-app/middleware.ts | 46 +++++++++++++++++++++++++++------- FrontEnd/my-app/next.config.ts | 5 ++-- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/FrontEnd/my-app/middleware.ts b/FrontEnd/my-app/middleware.ts index 530c1de60..c9c704b81 100644 --- a/FrontEnd/my-app/middleware.ts +++ b/FrontEnd/my-app/middleware.ts @@ -1,21 +1,49 @@ +import { NextResponse } from 'next/server'; import createMiddleware from 'next-intl/middleware'; import { locales, defaultLocale } from '@/lib/i18n/config'; +import crypto from 'crypto'; +import { cspHeaders } from './next.config.csp'; -export default createMiddleware({ - // A list of all locales that are supported +const intlMiddleware = createMiddleware({ locales, - - // Used when no locale matches defaultLocale, - - // Language detection localeDetection: true, - - // Locale prefix - always add the locale to the URL localePrefix: 'always', }); +export function middleware(request) { + // Generate nonce per request + const nonce = crypto.randomBytes(16).toString('base64'); + + // Run intl middleware first + const intlResponse = intlMiddleware(request); + + // Determine CSP header key based on environment + const isDev = process.env.NODE_ENV !== 'production'; + const headerKey = isDev ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; + + // Build CSP headers with nonce replacing unsafe-inline + const modifiedHeaders = cspHeaders.map(h => { + if (h.key === 'Content-Security-Policy') { + const value = h.value.replace(/'unsafe-inline'/g, `'nonce-${nonce}'`); + return { key: headerKey, value }; + } + return h; + }); + + // Prepare response object + const response = intlResponse instanceof NextResponse ? intlResponse : NextResponse.next(); + + // Set CSP headers + modifiedHeaders.forEach(h => { + response.headers.set(h.key, h.value); + }); + // Expose nonce to client if needed + response.headers.set('X-Content-Nonce', nonce); + + return response; +} + export const config = { - // Match only internationalized pathnames matcher: ['/', '/(es|en)/:path*', '/((?!api|_next|_static|.*\\..*).*)'], }; diff --git a/FrontEnd/my-app/next.config.ts b/FrontEnd/my-app/next.config.ts index 9c8c8e5dd..211fff09e 100644 --- a/FrontEnd/my-app/next.config.ts +++ b/FrontEnd/my-app/next.config.ts @@ -12,8 +12,9 @@ const nextConfig: NextConfig = { async headers() { const isDev = process.env.NODE_ENV !== 'production'; const headerKey = isDev ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; - // Replace the CSP header key based on environment - const modifiedHeaders = cspHeaders.map(h => { + // Flatten cspHeaders and replace key based on environment + const baseHeaders = cspHeaders[0].headers; + const modifiedHeaders = baseHeaders.map(h => { if (h.key === 'Content-Security-Policy') { return { key: headerKey, value: h.value }; } From 658a74fbc4cec939df3bcc66a827d92f56f12fcb Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Wed, 1 Jul 2026 15:25:31 +0100 Subject: [PATCH 3/8] fix: resolve typescript compilation errors in middleware.ts --- FrontEnd/my-app/middleware.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/FrontEnd/my-app/middleware.ts b/FrontEnd/my-app/middleware.ts index c9c704b81..289b2ee9e 100644 --- a/FrontEnd/my-app/middleware.ts +++ b/FrontEnd/my-app/middleware.ts @@ -1,4 +1,4 @@ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; import createMiddleware from 'next-intl/middleware'; import { locales, defaultLocale } from '@/lib/i18n/config'; import crypto from 'crypto'; @@ -11,7 +11,7 @@ const intlMiddleware = createMiddleware({ localePrefix: 'always', }); -export function middleware(request) { +export function middleware(request: NextRequest) { // Generate nonce per request const nonce = crypto.randomBytes(16).toString('base64'); @@ -22,22 +22,23 @@ export function middleware(request) { const isDev = process.env.NODE_ENV !== 'production'; const headerKey = isDev ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; - // Build CSP headers with nonce replacing unsafe-inline - const modifiedHeaders = cspHeaders.map(h => { - if (h.key === 'Content-Security-Policy') { - const value = h.value.replace(/'unsafe-inline'/g, `'nonce-${nonce}'`); - return { key: headerKey, value }; - } - return h; - }); - // Prepare response object const response = intlResponse instanceof NextResponse ? intlResponse : NextResponse.next(); - // Set CSP headers - modifiedHeaders.forEach(h => { - response.headers.set(h.key, h.value); + // Fix: Properly handle cspHeaders structure and replace key based on environment + cspHeaders.forEach(config => { + if (config.headers && Array.isArray(config.headers)) { + config.headers.forEach(header => { + if (header.key === 'Content-Security-Policy') { + const value = header.value.replace(/'unsafe-inline'/g, `'nonce-${nonce}'`); + response.headers.set(headerKey, value); + } else { + response.headers.set(header.key, header.value); + } + }); + } }); + // Expose nonce to client if needed response.headers.set('X-Content-Nonce', nonce); From 69c0816b5f1128b6593edfe4a72a27063db64a2a Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Wed, 1 Jul 2026 16:18:47 +0100 Subject: [PATCH 4/8] style: format middleware.ts and next.config.ts with Prettier rules --- FrontEnd/my-app/middleware.ts | 19 ++++++++++++++----- FrontEnd/my-app/next.config.ts | 7 +++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/FrontEnd/my-app/middleware.ts b/FrontEnd/my-app/middleware.ts index 289b2ee9e..298985658 100644 --- a/FrontEnd/my-app/middleware.ts +++ b/FrontEnd/my-app/middleware.ts @@ -20,17 +20,25 @@ export function middleware(request: NextRequest) { // Determine CSP header key based on environment const isDev = process.env.NODE_ENV !== 'production'; - const headerKey = isDev ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; + const headerKey = isDev + ? 'Content-Security-Policy-Report-Only' + : 'Content-Security-Policy'; // Prepare response object - const response = intlResponse instanceof NextResponse ? intlResponse : NextResponse.next(); + const response = + intlResponse instanceof NextResponse + ? intlResponse + : NextResponse.next(); // Fix: Properly handle cspHeaders structure and replace key based on environment - cspHeaders.forEach(config => { + cspHeaders.forEach((config) => { if (config.headers && Array.isArray(config.headers)) { - config.headers.forEach(header => { + config.headers.forEach((header) => { if (header.key === 'Content-Security-Policy') { - const value = header.value.replace(/'unsafe-inline'/g, `'nonce-${nonce}'`); + const value = header.value.replace( + /'unsafe-inline'/g, + `'nonce-${nonce}'` + ); response.headers.set(headerKey, value); } else { response.headers.set(header.key, header.value); @@ -48,3 +56,4 @@ export function middleware(request: NextRequest) { export const config = { matcher: ['/', '/(es|en)/:path*', '/((?!api|_next|_static|.*\\..*).*)'], }; + diff --git a/FrontEnd/my-app/next.config.ts b/FrontEnd/my-app/next.config.ts index 211fff09e..5046a3fb8 100644 --- a/FrontEnd/my-app/next.config.ts +++ b/FrontEnd/my-app/next.config.ts @@ -11,10 +11,12 @@ const nextConfig: NextConfig = { outputFileTracingRoot: __dirname, async headers() { const isDev = process.env.NODE_ENV !== 'production'; - const headerKey = isDev ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; + const headerKey = isDev + ? 'Content-Security-Policy-Report-Only' + : 'Content-Security-Policy'; // Flatten cspHeaders and replace key based on environment const baseHeaders = cspHeaders[0].headers; - const modifiedHeaders = baseHeaders.map(h => { + const modifiedHeaders = baseHeaders.map((h) => { if (h.key === 'Content-Security-Policy') { return { key: headerKey, value: h.value }; } @@ -31,3 +33,4 @@ export default withSentryConfig(withAnalyzer(nextConfig), { sourcemaps: { disable: true }, silent: process.env.CI === 'true', }); + From 23f4322c64c5bf592ab353835038ac665fb3cd47 Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Wed, 1 Jul 2026 19:05:40 +0100 Subject: [PATCH 5/8] fix: resolve linter warnings for unused imports and any cast type violations in error boundary files --- .../error/APIBootstrapErrorBoundary.tsx | 20 ++++++++++--------- .../my-app/components/error/ErrorBoundary.tsx | 3 +-- FrontEnd/my-app/lib/api/client.ts | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx b/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx index dfa97f858..861b959f8 100644 --- a/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx +++ b/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx @@ -100,22 +100,24 @@ export class APIBootstrapErrorBoundary extends React.Component< }; static getDerivedStateFromError( - error: Error | AppError + error: Error | AppError | Record ): Partial { + const errorWithCode = error as Record; const isNetworkError = - error.message.toLowerCase().includes('offline') || - error.message.toLowerCase().includes('network') || - error.message.toLowerCase().includes('unreachable') || - (error as any).code === 'ERR_NETWORK' || - (error as any).code === 'NETWORK_ERROR' || - (error as any).code === 'TIMEOUT_ERROR'; + (error instanceof Error || (error && typeof error.message === 'string')) && + (error.message.toLowerCase().includes('offline') || + error.message.toLowerCase().includes('network') || + error.message.toLowerCase().includes('unreachable') || + errorWithCode.code === 'ERR_NETWORK' || + errorWithCode.code === 'NETWORK_ERROR' || + errorWithCode.code === 'TIMEOUT_ERROR'); return { hasError: true, - error, + error: error as Error | AppError, errorInfo: null, isOffline: - isNetworkError || + !!isNetworkError || (typeof navigator !== 'undefined' && !navigator.onLine), }; } diff --git a/FrontEnd/my-app/components/error/ErrorBoundary.tsx b/FrontEnd/my-app/components/error/ErrorBoundary.tsx index 6c8703434..44b9e719c 100644 --- a/FrontEnd/my-app/components/error/ErrorBoundary.tsx +++ b/FrontEnd/my-app/components/error/ErrorBoundary.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { ErrorMessage } from './ErrorMessage'; -import { logError, ERROR_CODES } from '@/lib/utils/error-handler'; -import type { AppError } from '@/lib/utils/error-handler'; +import { logError } from '@/lib/utils/error-handler'; interface ErrorBoundaryProps { children: React.ReactNode; diff --git a/FrontEnd/my-app/lib/api/client.ts b/FrontEnd/my-app/lib/api/client.ts index 3dc658e41..a1af46d52 100644 --- a/FrontEnd/my-app/lib/api/client.ts +++ b/FrontEnd/my-app/lib/api/client.ts @@ -22,7 +22,7 @@ import { type AppError, } from '@/lib/utils/error-handler'; import { mapApiError, inferDomainFromUrl } from '@/lib/api/api-error-mapper'; -import type { ApiErrorResponse, AuthTokens } from '@/lib/types/api.types'; +import type { AuthTokens } from '@/lib/types/api.types'; import { env } from '@/lib/config/env'; // --------------------------------------------------------------------------- From d9dcc59d08f4558051cc1b04d84ef60310bc8609 Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Thu, 2 Jul 2026 10:19:13 +0100 Subject: [PATCH 6/8] fix: safely type check and extract errorMessage to avoid TS compiler error --- .../error/APIBootstrapErrorBoundary.tsx | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx b/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx index 861b959f8..07ffa6b0d 100644 --- a/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx +++ b/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx @@ -103,14 +103,26 @@ export class APIBootstrapErrorBoundary extends React.Component< error: Error | AppError | Record ): Partial { const errorWithCode = error as Record; + + let errorMessage = ''; + if (error instanceof Error) { + errorMessage = error.message; + } else if ( + error && + typeof error === 'object' && + 'message' in error && + typeof (error as Record).message === 'string' + ) { + errorMessage = (error as Record).message as string; + } + const isNetworkError = - (error instanceof Error || (error && typeof error.message === 'string')) && - (error.message.toLowerCase().includes('offline') || - error.message.toLowerCase().includes('network') || - error.message.toLowerCase().includes('unreachable') || - errorWithCode.code === 'ERR_NETWORK' || - errorWithCode.code === 'NETWORK_ERROR' || - errorWithCode.code === 'TIMEOUT_ERROR'); + errorMessage.toLowerCase().includes('offline') || + errorMessage.toLowerCase().includes('network') || + errorMessage.toLowerCase().includes('unreachable') || + errorWithCode.code === 'ERR_NETWORK' || + errorWithCode.code === 'NETWORK_ERROR' || + errorWithCode.code === 'TIMEOUT_ERROR'; return { hasError: true, From ea587dde3867de6e2130a19cdeb69d74b362b5bd Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Thu, 2 Jul 2026 10:33:43 +0100 Subject: [PATCH 7/8] style: remove trailing blank lines in middleware and config files for Prettier compliance --- FrontEnd/my-app/middleware.ts | 1 - FrontEnd/my-app/next.config.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/FrontEnd/my-app/middleware.ts b/FrontEnd/my-app/middleware.ts index 298985658..157c77bd0 100644 --- a/FrontEnd/my-app/middleware.ts +++ b/FrontEnd/my-app/middleware.ts @@ -56,4 +56,3 @@ export function middleware(request: NextRequest) { export const config = { matcher: ['/', '/(es|en)/:path*', '/((?!api|_next|_static|.*\\..*).*)'], }; - diff --git a/FrontEnd/my-app/next.config.ts b/FrontEnd/my-app/next.config.ts index 5046a3fb8..2c4e3aa2c 100644 --- a/FrontEnd/my-app/next.config.ts +++ b/FrontEnd/my-app/next.config.ts @@ -33,4 +33,3 @@ export default withSentryConfig(withAnalyzer(nextConfig), { sourcemaps: { disable: true }, silent: process.env.CI === 'true', }); - From 702d418c957c1a96a2fc0f28be02e8d8d32aa56a Mon Sep 17 00:00:00 2001 From: Opulence Chuks Date: Fri, 3 Jul 2026 02:45:28 +0100 Subject: [PATCH 8/8] style: add trailing comma in middleware.ts for Prettier alignment --- FrontEnd/my-app/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FrontEnd/my-app/middleware.ts b/FrontEnd/my-app/middleware.ts index 157c77bd0..f264ce636 100644 --- a/FrontEnd/my-app/middleware.ts +++ b/FrontEnd/my-app/middleware.ts @@ -37,7 +37,7 @@ export function middleware(request: NextRequest) { if (header.key === 'Content-Security-Policy') { const value = header.value.replace( /'unsafe-inline'/g, - `'nonce-${nonce}'` + `'nonce-${nonce}'`, ); response.headers.set(headerKey, value); } else {