diff --git a/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx b/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx index dfa97f858..07ffa6b0d 100644 --- a/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx +++ b/FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx @@ -100,22 +100,36 @@ export class APIBootstrapErrorBoundary extends React.Component< }; static getDerivedStateFromError( - error: Error | AppError + 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.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'; + 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, - 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'; // --------------------------------------------------------------------------- diff --git a/FrontEnd/my-app/middleware.ts b/FrontEnd/my-app/middleware.ts index 530c1de60..f264ce636 100644 --- a/FrontEnd/my-app/middleware.ts +++ b/FrontEnd/my-app/middleware.ts @@ -1,21 +1,58 @@ +import { NextRequest, 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: NextRequest) { + // 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'; + + // Prepare response object + const response = + intlResponse instanceof NextResponse + ? intlResponse + : NextResponse.next(); + + // 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); + + 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 d416a63ca..2c4e3aa2c 100644 --- a/FrontEnd/my-app/next.config.ts +++ b/FrontEnd/my-app/next.config.ts @@ -10,7 +10,19 @@ 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'; + // 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 }; + } + return h; + }); + return [{ source: '/(.*)', headers: modifiedHeaders }]; }, };