Skip to content
Open
32 changes: 23 additions & 9 deletions FrontEnd/my-app/components/error/APIBootstrapErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,36 @@ export class APIBootstrapErrorBoundary extends React.Component<
};

static getDerivedStateFromError(
error: Error | AppError
error: Error | AppError | Record<string, unknown>
): Partial<APIBootstrapErrorState> {
const errorWithCode = error as Record<string, unknown>;

let errorMessage = '';
if (error instanceof Error) {
errorMessage = error.message;
} else if (
error &&
typeof error === 'object' &&
'message' in error &&
typeof (error as Record<string, unknown>).message === 'string'
) {
errorMessage = (error as Record<string, unknown>).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),
};
}
Expand Down
3 changes: 1 addition & 2 deletions FrontEnd/my-app/components/error/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion FrontEnd/my-app/lib/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

// ---------------------------------------------------------------------------
Expand Down
55 changes: 46 additions & 9 deletions FrontEnd/my-app/middleware.ts
Original file line number Diff line number Diff line change
@@ -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|.*\\..*).*)'],
};
14 changes: 13 additions & 1 deletion FrontEnd/my-app/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }];
},
};

Expand Down
Loading