diff --git a/next.config.js b/next.config.js deleted file mode 100644 index 1281d30..0000000 --- a/next.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - experimental: { - // Optimiser les imports pour réduire la mémoire - optimizePackageImports: ['@mui/material', '@mui/icons-material', '@emotion/react', '@emotion/styled'], - }, - // Réduire l'utilisation mémoire - compress: true, - // Désactiver les source maps en dev pour économiser la mémoire - productionBrowserSourceMaps: false, -} - -module.exports = nextConfig diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..e828713 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,73 @@ +/** @type {import('next').NextConfig} */ + +// Les URL Directus sont lues à l'exécution — elles s'adaptent à l'environnement +// (dev local ou production) sans rebuild. +const apiUrl = process.env.NEXT_PUBLIC_DIRECTUS_API_URL ?? '' +const wsUrl = process.env.NEXT_PUBLIC_DIRECTUS_API_WS_URL ?? '' + +// Tokens CSP — les guillemets simples font partie de la spec CSP, pas de JS +const SELF = '\'self\'' +const NONE = '\'none\'' +const UNSAFE_INLINE = '\'unsafe-inline\'' + +// Content Security Policy +// - unsafe-inline sur script-src : requis par Next.js App Router (hydratation inline) +// - unsafe-inline sur style-src : requis par Emotion/MUI (styles injectés dynamiquement) +// - data: blob: sur img-src : requis par html2canvas (export PDF) +// - frame-ancestors 'none' : anti-clickjacking (complète X-Frame-Options) +const cspDirectives = [ + `default-src ${SELF}`, + `script-src ${SELF} ${UNSAFE_INLINE}`, + `style-src ${SELF} ${UNSAFE_INLINE}`, + `connect-src ${SELF} ${apiUrl} ${wsUrl}`.trim(), + `img-src ${SELF} data: blob:`, + `font-src ${SELF}`, + `object-src ${NONE}`, + `base-uri ${SELF}`, + `form-action ${SELF}`, + `frame-ancestors ${NONE}`, +] + +const securityHeaders = [ + { + key: 'Content-Security-Policy', + value: cspDirectives.join('; '), + }, + { + key: 'X-Frame-Options', + value: 'DENY', + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + { + key: 'Permissions-Policy', + value: 'camera=(), microphone=(), geolocation=()', + }, +] + +const nextConfig = { + experimental: { + // Optimiser les imports pour réduire la mémoire + optimizePackageImports: ['@mui/material', '@mui/icons-material', '@emotion/react', '@emotion/styled'], + }, + // Réduire l'utilisation mémoire + compress: true, + // Désactiver les source maps en dev pour économiser la mémoire + productionBrowserSourceMaps: false, + async headers() { + return [ + { + source: '/(.*)', + headers: securityHeaders, + }, + ] + }, +} + +export default nextConfig diff --git a/tasks/todo.md b/tasks/todo.md index 6fd565f..cd3e77c 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -10,7 +10,7 @@ ## Améliorations hautes (P2) -- [ ] **Headers CSP** — ajouter dans `next.config.js` +- [x] **Headers CSP** — `next.config.mjs` (renommé depuis .js) avec CSP + 4 headers sécurité - [ ] **Tests unitaires** — Vitest sur `lib/format.js`, `lib/version-utils.js`, `lib/rate-limit.js` - [ ] **Tests extensions Directus** — mocks VersionsService - [ ] **Refresh token explicite** — callback `jwt` dans NextAuth options