# 🔍 AUDIT COMPLET - PROJET NEAH ## Next.js 16.1.1 (App Router) - Audit Performance, Qualité, Sécurité & RGPD **Date de l'audit :** $(date) **Version Next.js :** 16.1.1 **Architecture :** App Router **TypeScript :** ✅ Activé --- ## 📊 RÉSUMÉ EXÉCUTIF | Catégorie | Score | Statut | |-----------|-------|--------| | **Performance** | 55/100 | ⚠️ À améliorer | | **Qualité du Code** | 60/100 | ⚠️ Intermédiaire | | **Sécurité** | 50/100 | ⚠️ Critique | | **RGPD** | 40/100 | 🚫 Non conforme | | **SCORE GLOBAL** | **51/100** | ⚠️ **Prêt avec correctifs critiques** | ### 🎯 Verdict Final **⚠️ PRÊT AVEC CORRECTIFS CRITIQUES** Le projet nécessite des correctifs importants avant la mise en production, notamment sur la sécurité et la conformité RGPD. --- ## 1️⃣ PERFORMANCE & OPTIMISATION ### ✅ Points Forts 1. **Next.js 16.1.1** - Version récente avec App Router 2. **next/font** - Utilisation correcte de `Inter` avec `next/font/google` 3. **Dynamic imports** - Utilisation de `dynamic()` et `import()` pour le code splitting - Exemple : `components/observatory/observatory-map.tsx` utilise `dynamic()` pour Leaflet - Plusieurs imports dynamiques dans les services (caldav-sync, microsoft-calendar-sync) 4. **Redis caching** - Système de cache Redis implémenté 5. **Health check endpoint** - `/api/health` pour monitoring ### 🚨 Problèmes Détectés #### 1. **Images non optimisées** ⚠️ CRITIQUE ```javascript // next.config.mjs ligne 9-11 images: { unoptimized: true, // ❌ DÉSACTIVE l'optimisation Next.js } ``` **Impact :** - Pas de lazy loading automatique - Pas de conversion WebP/AVIF - Pas de responsive images - Augmentation du poids des pages **Exemples dans le code :** - `app/missions/page.tsx` ligne 238 : `` au lieu de `` - `app/mission-tab/[missionId]/page.tsx` ligne 222 : `` natif - Seul `components/podcast.tsx` utilise `next/image` correctement #### 2. **Page principale en Client Component** ⚠️ IMPORTANT ```typescript // app/page.tsx ligne 1 "use client"; // ❌ Toute la page est client-side ``` **Impact :** - Pas de SSR pour la page d'accueil - Hydration complète côté client - Pas de streaming - TTFB plus élevé #### 3. **Pas de metadata dans le layout principal** ⚠️ IMPORTANT ```typescript // app/layout.tsx // ❌ Pas de metadata exportée ``` **Impact :** - Pas de SEO de base - Pas d'Open Graph - Pas de description #### 4. **Utilisation excessive de localStorage/sessionStorage** ⚠️ MOYEN - 72 occurrences de `localStorage`/`sessionStorage` - Stockage de données utilisateur sans consentement explicite - Risque RGPD (voir section 3) #### 5. **Pas de Suspense boundaries** ⚠️ MOYEN - Aucun fichier `loading.tsx` trouvé - Pas de gestion de loading states avec Suspense - Spinners manuels dans les composants #### 6. **Pas de error.tsx** ⚠️ MOYEN - Aucun fichier `error.tsx` trouvé - Pas de gestion d'erreurs au niveau des routes - Gestion d'erreurs uniquement dans les composants #### 7. **Bundle size non optimisé** ⚠️ MOYEN - Beaucoup de dépendances lourdes chargées - Pas de tree-shaking visible - Pas d'analyse de bundle size ### 📋 Recommandations Actionnables #### 🔴 CRITIQUE - Activer l'optimisation d'images ```typescript // next.config.mjs const nextConfig = { images: { unoptimized: false, // ✅ Activer formats: ['image/avif', 'image/webp'], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], remotePatterns: [ { protocol: 'https', hostname: '**', }, ], }, }; ``` Remplacer tous les `` par `` : ```typescript // Avant {mission.name} // Après import Image from 'next/image'; {mission.name} ``` #### 🟡 IMPORTANT - Convertir la page principale en Server Component ```typescript // app/page.tsx - Nouvelle version import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/options"; import { QuoteCard } from "@/components/quote-card"; import { Calendar } from "@/components/calendar"; import { News } from "@/components/news"; import { Duties } from "@/components/flow"; import { Email } from "@/components/email"; import { Parole } from "@/components/parole"; import { Suspense } from "react"; export default async function Home() { const session = await getServerSession(authOptions); if (!session) { redirect("/signin"); } return (
}> }> }> }>
}> }>
); } ``` #### 🟡 IMPORTANT - Ajouter metadata au layout ```typescript // app/layout.tsx import type { Metadata } from "next"; export const metadata: Metadata = { title: { default: "NEAH - Plateforme de gestion", template: "%s | NEAH" }, description: "Plateforme de gestion NEAH", keywords: ["NEAH", "gestion", "missions"], authors: [{ name: "NEAH Team" }], openGraph: { type: "website", locale: "fr_FR", url: process.env.NEXTAUTH_URL, siteName: "NEAH", title: "NEAH - Plateforme de gestion", description: "Plateforme de gestion NEAH", }, robots: { index: true, follow: true, }, }; ``` #### 🟡 IMPORTANT - Créer loading.tsx et error.tsx ```typescript // app/loading.tsx export default function Loading() { return (
); } ``` ```typescript // app/error.tsx 'use client'; import { useEffect } from 'react'; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { console.error(error); }, [error]); return (

Une erreur est survenue

); } ``` #### 🟢 MOYEN - Optimiser le bundle size ```bash # Ajouter à package.json "analyze": "ANALYZE=true next build" ``` Installer `@next/bundle-analyzer` : ```bash npm install --save-dev @next/bundle-analyzer ``` ```typescript // next.config.mjs const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', }); module.exports = withBundleAnalyzer(nextConfig); ``` --- ## 2️⃣ BONNES PRATIQUES NEXT.JS & CODE QUALITY ### ✅ Points Forts 1. **Structure du projet** - Organisation claire avec `app/`, `components/`, `lib/` 2. **TypeScript** - Utilisation de TypeScript avec configuration stricte 3. **Séparation des responsabilités** - Services séparés (`lib/services/`) 4. **Validation d'environnement** - Script `validate-env.ts` présent 5. **Logger centralisé** - `lib/logger.ts` avec niveaux de log 6. **Gestion d'erreurs API** - Certaines routes ont une bonne gestion d'erreurs 7. **Health check** - Endpoint `/api/health` pour monitoring ### 🚨 Problèmes Détectés #### 1. **ESLint et TypeScript désactivés en build** 🚨 CRITIQUE ```javascript // next.config.mjs ligne 3-8 eslint: { ignoreDuringBuilds: true, // ❌ DANGEREUX }, typescript: { ignoreBuildErrors: true, // ❌ DANGEREUX }, ``` **Impact :** - Erreurs TypeScript ignorées - Erreurs ESLint ignorées - Risque de bugs en production #### 2. **Pas de tests** 🚨 CRITIQUE - Aucun fichier `.test.ts` ou `.spec.ts` trouvé - Pas de tests unitaires - Pas de tests e2e - Pas de coverage #### 3. **Pas de metadata SEO** ⚠️ IMPORTANT - Metadata manquante dans la plupart des pages - Pas de sitemap - Pas de robots.txt - Pas d'Open Graph complet #### 4. **Gestion d'erreurs incomplète** ⚠️ IMPORTANT - Pas de `error.tsx` au niveau des routes - Pas de `not-found.tsx` global - Gestion d'erreurs incohérente dans les API routes #### 5. **Accessibilité (a11y) non vérifiée** ⚠️ MOYEN - Pas de vérification d'accessibilité visible - Pas d'attributs ARIA - Contraste des couleurs non vérifié #### 6. **Console.log en production** ⚠️ MOYEN - Beaucoup de `console.log` dans le code - Logger configuré mais pas utilisé partout - Risque de fuite d'informations #### 7. **Pas de rate limiting** ⚠️ MOYEN - Pas de protection contre les abus - API routes exposées sans limitation ### 📋 Recommandations Actionnables #### 🔴 CRITIQUE - Réactiver ESLint et TypeScript ```typescript // next.config.mjs const nextConfig = { eslint: { ignoreDuringBuilds: false, // ✅ Réactiver dirs: ['app', 'components', 'lib'], // Limiter aux dossiers pertinents }, typescript: { ignoreBuildErrors: false, // ✅ Réactiver }, }; ``` **Action immédiate :** Corriger toutes les erreurs TypeScript et ESLint avant de réactiver. #### 🔴 CRITIQUE - Ajouter des tests ```bash # Installer les dépendances de test npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom @types/jest ``` ```json // package.json { "scripts": { "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage" } } ``` ```typescript // jest.config.js const nextJest = require('next/jest') const createJestConfig = nextJest({ dir: './', }) const customJestConfig = { setupFilesAfterEnv: ['/jest.setup.js'], testEnvironment: 'jest-environment-jsdom', moduleNameMapper: { '^@/(.*)$': '/$1', }, } module.exports = createJestConfig(customJestConfig) ``` Exemple de test : ```typescript // __tests__/components/quote-card.test.tsx import { render, screen } from '@testing-library/react'; import { QuoteCard } from '@/components/quote-card'; describe('QuoteCard', () => { it('renders without crashing', () => { render(); expect(screen.getByRole('article')).toBeInTheDocument(); }); }); ``` #### 🟡 IMPORTANT - Créer sitemap et robots.txt ```typescript // app/sitemap.ts import { MetadataRoute } from 'next'; export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = process.env.NEXTAUTH_URL || 'https://example.com'; return [ { url: baseUrl, lastModified: new Date(), changeFrequency: 'daily', priority: 1, }, { url: `${baseUrl}/missions`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8, }, // ... autres routes ]; } ``` ```typescript // app/robots.ts import { MetadataRoute } from 'next'; export default function robots(): MetadataRoute.Robots { return { rules: { userAgent: '*', allow: '/', disallow: ['/api/', '/signin', '/signout'], }, sitemap: `${process.env.NEXTAUTH_URL}/sitemap.xml`, }; } ``` #### 🟡 IMPORTANT - Créer not-found.tsx ```typescript // app/not-found.tsx import Link from 'next/link'; export default function NotFound() { return (

404

Page non trouvée

Retour à l'accueil
); } ``` #### 🟡 MOYEN - Ajouter rate limiting ```bash npm install next-rate-limit ``` ```typescript // lib/rate-limit.ts import { rateLimit } from 'next-rate-limit'; export const limiter = rateLimit({ interval: 60 * 1000, // 1 minute uniqueTokenPerInterval: 500, }); ``` ```typescript // app/api/example/route.ts import { limiter } from '@/lib/rate-limit'; export async function GET(request: Request) { await limiter.check(10, 'CACHE_TOKEN'); // 10 requêtes par minute // ... reste du code } ``` #### 🟢 MOYEN - Améliorer l'accessibilité ```typescript // Exemple : Ajouter des attributs ARIA ``` Installer un linter d'accessibilité : ```bash npm install --save-dev eslint-plugin-jsx-a11y ``` ### 📊 Niveau de Maturité **Niveau : Intermédiaire** ⚠️ **Justification :** - ✅ Structure de projet solide - ✅ TypeScript bien utilisé - ❌ Pas de tests - ❌ ESLint/TypeScript désactivés - ⚠️ Gestion d'erreurs partielle - ⚠️ SEO incomplet --- ## 3️⃣ CONFORMITÉ RGPD (GDPR) ### 🚨 Problèmes Détectés #### 1. **Stockage de données personnelles sans consentement** 🚨 CRITIQUE **localStorage/sessionStorage utilisé massivement :** - 72 occurrences dans le code - Stockage d'IDs utilisateur, tokens, données de session - Pas de consentement explicite - Pas de politique de rétention **Exemples problématiques :** ```typescript // hooks/use-task-notifications.ts localStorage.setItem('notified-task-ids', JSON.stringify(ids)); // hooks/use-calendar-event-notifications.ts localStorage.setItem('notified-event-ids', JSON.stringify(ids)); // lib/cache-utils.ts localStorage.setItem(fullKey, JSON.stringify(entry)); // app/page.tsx sessionStorage.setItem('just_logged_out', 'true'); ``` #### 2. **Pas de politique de confidentialité visible** 🚨 CRITIQUE - Aucune page `/privacy` ou `/confidentialite` trouvée - Pas de mention légale - Pas de politique de cookies #### 3. **Pas de gestion des droits utilisateurs** 🚨 CRITIQUE - Pas d'endpoint pour accès aux données (Article 15) - Pas d'endpoint pour rectification (Article 16) - Pas d'endpoint pour suppression (Article 17 - droit à l'oubli) - Pas d'export des données (Article 20) #### 4. **Cookies sans consentement** ⚠️ IMPORTANT - Cookies Next-Auth utilisés (légitimes pour l'authentification) - Mais pas de banner de consentement pour les cookies non essentiels - Pas de distinction cookies essentiels / non essentiels #### 5. **Données partagées avec des tiers** ⚠️ IMPORTANT - Keycloak (authentification) - RocketChat (messages) - Microsoft Graph (emails, calendrier) - Leantime (tâches) - Pas de mention explicite dans une politique de confidentialité #### 6. **Logs contenant potentiellement des données personnelles** ⚠️ MOYEN ```typescript // lib/logger.ts // Pas de sanitization des données avant logging logger.debug('User data', { userId, email }); // Risque RGPD ``` #### 7. **Pas de minimisation des données** ⚠️ MOYEN - Stockage de données non nécessaires - Pas de nettoyage automatique des données obsolètes ### 📋 Recommandations Actionnables #### 🔴 CRITIQUE - Créer une politique de confidentialité ```typescript // app/confidentialite/page.tsx import { Metadata } from 'next'; export const metadata: Metadata = { title: 'Politique de Confidentialité', }; export default function ConfidentialitePage() { return (

Politique de Confidentialité

1. Collecte des données

Nous collectons les données suivantes :

  • Données d'authentification (via Keycloak)
  • Emails et calendriers (via Microsoft Graph)
  • Messages (via RocketChat)
  • Tâches (via Leantime)

2. Base légale

Le traitement est basé sur :

  • Votre consentement
  • L'exécution d'un contrat
  • L'intérêt légitime

3. Vos droits

Conformément au RGPD, vous disposez des droits suivants :

  • Droit d'accès (Article 15)
  • Droit de rectification (Article 16)
  • Droit à l'effacement (Article 17)
  • Droit à la portabilité (Article 20)
  • Droit d'opposition (Article 21)

4. Contact

Pour exercer vos droits, contactez : privacy@example.com

); } ``` #### 🔴 CRITIQUE - Implémenter les droits utilisateurs ```typescript // app/api/gdpr/access/route.ts import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/options'; import { prisma } from '@/lib/prisma'; export async function GET() { const session = await getServerSession(authOptions); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } // Collecter toutes les données de l'utilisateur const userData = { profile: await prisma.user.findUnique({ where: { id: session.user.id }, }), emails: await prisma.email.findMany({ where: { userId: session.user.id }, }), calendars: await prisma.calendar.findMany({ where: { userId: session.user.id }, }), // ... autres données }; return NextResponse.json(userData); } ``` ```typescript // app/api/gdpr/delete/route.ts export async function DELETE() { const session = await getServerSession(authOptions); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } // Supprimer toutes les données de l'utilisateur await prisma.user.delete({ where: { id: session.user.id }, }); // Nettoyer localStorage côté client // (nécessite une action côté client) return NextResponse.json({ success: true }); } ``` #### 🔴 CRITIQUE - Ajouter un banner de consentement cookies ```typescript // components/cookie-consent.tsx 'use client'; import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; export function CookieConsent() { const [showBanner, setShowBanner] = useState(false); useEffect(() => { const consent = localStorage.getItem('cookie-consent'); if (!consent) { setShowBanner(true); } }, []); const acceptCookies = () => { localStorage.setItem('cookie-consent', 'accepted'); localStorage.setItem('cookie-consent-date', new Date().toISOString()); setShowBanner(false); }; const rejectCookies = () => { localStorage.setItem('cookie-consent', 'rejected'); // Supprimer les cookies non essentiels // ... setShowBanner(false); }; if (!showBanner) return null; return (

Nous utilisons des cookies pour améliorer votre expérience. En savoir plus

); } ``` #### 🟡 IMPORTANT - Minimiser le stockage localStorage ```typescript // lib/storage-utils.ts export class GDPRCompliantStorage { private static getConsent(): boolean { return localStorage.getItem('cookie-consent') === 'accepted'; } static setItem(key: string, value: string): void { if (!this.getConsent()) { console.warn('Storage blocked: no consent'); return; } // Ajouter une date d'expiration const entry = { value, expiresAt: Date.now() + (30 * 24 * 60 * 60 * 1000), // 30 jours }; localStorage.setItem(key, JSON.stringify(entry)); } static getItem(key: string): string | null { if (!this.getConsent()) { return null; } const item = localStorage.getItem(key); if (!item) return null; try { const entry = JSON.parse(item); if (entry.expiresAt && Date.now() > entry.expiresAt) { localStorage.removeItem(key); return null; } return entry.value; } catch { return item; // Fallback pour compatibilité } } static clearUserData(userId: string): void { // Nettoyer toutes les données d'un utilisateur const keys = Object.keys(localStorage); keys.forEach(key => { if (key.includes(userId) || key.startsWith('notified-')) { localStorage.removeItem(key); } }); } } ``` #### 🟡 IMPORTANT - Sanitizer les logs ```typescript // lib/logger.ts - Amélioration export const logger = { // ... existing code ... sanitize(data: any): any { if (!data || typeof data !== 'object') return data; const sensitive = ['password', 'token', 'secret', 'key', 'email']; const sanitized = { ...data }; for (const key in sanitized) { if (sensitive.some(s => key.toLowerCase().includes(s))) { sanitized[key] = '[REDACTED]'; } } return sanitized; }, debug(...args: any[]) { if (!isProd) { console.debug(...args.map(arg => typeof arg === 'object' ? this.sanitize(arg) : arg )); } }, }; ``` ### 📊 Niveau de Conformité **Niveau : Faible** 🚫 **Justification :** - ❌ Pas de politique de confidentialité - ❌ Pas de gestion des droits utilisateurs - ❌ Stockage localStorage sans consentement - ❌ Pas de banner cookies - ⚠️ Données partagées avec tiers non documentées - ⚠️ Logs non sanitizés --- ## 4️⃣ SÉCURITÉ ### ✅ Points Forts 1. **Next-Auth** - Utilisation de Next-Auth avec Keycloak 2. **Cookies sécurisés** - Configuration correcte des cookies (httpOnly, sameSite) 3. **DOMPurify** - Utilisé pour sanitizer le HTML (emails) 4. **Validation d'environnement** - Script de validation des variables d'environnement 5. **Health check** - Endpoint de monitoring ### 🚨 Problèmes Détectés #### 1. **Headers de sécurité incomplets** 🚨 CRITIQUE ```javascript // next.config.mjs ligne 20-31 async headers() { return [ { source: '/:path*', headers: [ { key: 'Content-Security-Policy', value: "frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net" // ❌ CSP incomplet, manque beaucoup de directives } ] } ] } ``` **Manque :** - X-Frame-Options - X-Content-Type-Options - X-XSS-Protection - Referrer-Policy - Permissions-Policy - Strict-Transport-Security (HSTS) #### 2. **CSP trop permissif** 🚨 CRITIQUE - CSP ne couvre que `frame-ancestors` - Pas de protection contre XSS - Pas de restriction des sources de scripts/styles #### 3. **Variables d'environnement exposées** ⚠️ IMPORTANT ```typescript // Beaucoup d'utilisation de process.env.NEXT_PUBLIC_* // Ces variables sont exposées au client ``` - Risque si des secrets sont dans `NEXT_PUBLIC_*` #### 4. **Pas de rate limiting** ⚠️ IMPORTANT - API routes non protégées contre les abus - Risque de DDoS - Risque de brute force #### 5. **Gestion d'erreurs trop verbeuse** ⚠️ MOYEN ```typescript // Certaines routes retournent des détails d'erreur return NextResponse.json({ error: "Failed", details: errorMessage, // ❌ Peut exposer des infos sensibles }); ``` #### 6. **Pas de CSRF protection explicite** ⚠️ MOYEN - Next-Auth gère le CSRF pour l'auth - Mais pas de protection explicite pour les autres endpoints #### 7. **Script inline dans le layout** ⚠️ MOYEN ```typescript // app/layout.tsx ligne 29-36