32 KiB
🔍 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
- Next.js 16.1.1 - Version récente avec App Router
- next/font - Utilisation correcte de
Interavecnext/font/google - Dynamic imports - Utilisation de
dynamic()etimport()pour le code splitting- Exemple :
components/observatory/observatory-map.tsxutilisedynamic()pour Leaflet - Plusieurs imports dynamiques dans les services (caldav-sync, microsoft-calendar-sync)
- Exemple :
- Redis caching - Système de cache Redis implémenté
- Health check endpoint -
/api/healthpour monitoring
🚨 Problèmes Détectés
1. Images non optimisées ⚠️ CRITIQUE
// 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.tsxligne 238 :<img>au lieu de<Image>app/mission-tab/[missionId]/page.tsxligne 222 :<img>natif- Seul
components/podcast.tsxutilisenext/imagecorrectement
2. Page principale en Client Component ⚠️ IMPORTANT
// 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
// 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.tsxtrouvé - Pas de gestion de loading states avec Suspense
- Spinners manuels dans les composants
6. Pas de error.tsx ⚠️ MOYEN
- Aucun fichier
error.tsxtrouvé - 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
// 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 <img> par <Image> :
// Avant
<img src={mission.logoUrl} alt={mission.name} />
// Après
import Image from 'next/image';
<Image
src={mission.logoUrl}
alt={mission.name}
width={192}
height={192}
className="w-full h-full object-cover rounded-md"
loading="lazy"
/>
🟡 IMPORTANT - Convertir la page principale en Server Component
// 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 (
<main className="h-screen overflow-auto">
<div className="container mx-auto p-4 mt-12">
<div className="grid grid-cols-12 gap-4 mb-4">
<Suspense fallback={<QuoteCardSkeleton />}>
<QuoteCard />
</Suspense>
<Suspense fallback={<CalendarSkeleton />}>
<Calendar />
</Suspense>
<Suspense fallback={<NewsSkeleton />}>
<News />
</Suspense>
<Suspense fallback={<DutiesSkeleton />}>
<Duties />
</Suspense>
</div>
<div className="grid grid-cols-12 gap-4">
<Suspense fallback={<EmailSkeleton />}>
<Email />
</Suspense>
<Suspense fallback={<ParoleSkeleton />}>
<Parole />
</Suspense>
</div>
</div>
</main>
);
}
🟡 IMPORTANT - Ajouter metadata au layout
// 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
// app/loading.tsx
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
</div>
);
}
// 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 (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold mb-4">Une erreur est survenue</h2>
<button
onClick={reset}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Réessayer
</button>
</div>
);
}
🟢 MOYEN - Optimiser le bundle size
# Ajouter à package.json
"analyze": "ANALYZE=true next build"
Installer @next/bundle-analyzer :
npm install --save-dev @next/bundle-analyzer
// 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
- Structure du projet - Organisation claire avec
app/,components/,lib/ - TypeScript - Utilisation de TypeScript avec configuration stricte
- Séparation des responsabilités - Services séparés (
lib/services/) - Validation d'environnement - Script
validate-env.tsprésent - Logger centralisé -
lib/logger.tsavec niveaux de log - Gestion d'erreurs API - Certaines routes ont une bonne gestion d'erreurs
- Health check - Endpoint
/api/healthpour monitoring
🚨 Problèmes Détectés
1. ESLint et TypeScript désactivés en build 🚨 CRITIQUE
// 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.tsou.spec.tstrouvé - 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.tsxau niveau des routes - Pas de
not-found.tsxglobal - 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.logdans 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
// 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
# Installer les dépendances de test
npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom @types/jest
// package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
// jest.config.js
const nextJest = require('next/jest')
const createJestConfig = nextJest({
dir: './',
})
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
},
}
module.exports = createJestConfig(customJestConfig)
Exemple de test :
// __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(<QuoteCard />);
expect(screen.getByRole('article')).toBeInTheDocument();
});
});
🟡 IMPORTANT - Créer sitemap et robots.txt
// 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
];
}
// 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
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-4xl font-bold mb-4">404</h2>
<p className="text-xl mb-8">Page non trouvée</p>
<Link
href="/"
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Retour à l'accueil
</Link>
</div>
);
}
🟡 MOYEN - Ajouter rate limiting
npm install next-rate-limit
// lib/rate-limit.ts
import { rateLimit } from 'next-rate-limit';
export const limiter = rateLimit({
interval: 60 * 1000, // 1 minute
uniqueTokenPerInterval: 500,
});
// 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é
// Exemple : Ajouter des attributs ARIA
<button
aria-label="Rafraîchir les emails"
aria-busy={isLoading}
onClick={handleRefresh}
>
<RefreshIcon />
</button>
Installer un linter d'accessibilité :
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 :
// 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
/privacyou/confidentialitetrouvé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
// 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é
// app/confidentialite/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Politique de Confidentialité',
};
export default function ConfidentialitePage() {
return (
<div className="container mx-auto p-8 max-w-4xl">
<h1 className="text-3xl font-bold mb-6">Politique de Confidentialité</h1>
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">1. Collecte des données</h2>
<p>Nous collectons les données suivantes :</p>
<ul className="list-disc ml-6">
<li>Données d'authentification (via Keycloak)</li>
<li>Emails et calendriers (via Microsoft Graph)</li>
<li>Messages (via RocketChat)</li>
<li>Tâches (via Leantime)</li>
</ul>
</section>
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">2. Base légale</h2>
<p>Le traitement est basé sur :</p>
<ul className="list-disc ml-6">
<li>Votre consentement</li>
<li>L'exécution d'un contrat</li>
<li>L'intérêt légitime</li>
</ul>
</section>
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">3. Vos droits</h2>
<p>Conformément au RGPD, vous disposez des droits suivants :</p>
<ul className="list-disc ml-6">
<li>Droit d'accès (Article 15)</li>
<li>Droit de rectification (Article 16)</li>
<li>Droit à l'effacement (Article 17)</li>
<li>Droit à la portabilité (Article 20)</li>
<li>Droit d'opposition (Article 21)</li>
</ul>
</section>
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">4. Contact</h2>
<p>Pour exercer vos droits, contactez : privacy@example.com</p>
</section>
</div>
);
}
🔴 CRITIQUE - Implémenter les droits utilisateurs
// 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);
}
// 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
// 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 (
<div className="fixed bottom-0 left-0 right-0 bg-gray-900 text-white p-4 z-50">
<div className="container mx-auto flex items-center justify-between">
<div>
<p className="text-sm">
Nous utilisons des cookies pour améliorer votre expérience.
<a href="/confidentialite" className="underline ml-1">
En savoir plus
</a>
</p>
</div>
<div className="flex gap-2">
<Button onClick={rejectCookies} variant="outline">
Refuser
</Button>
<Button onClick={acceptCookies}>
Accepter
</Button>
</div>
</div>
</div>
);
}
🟡 IMPORTANT - Minimiser le stockage localStorage
// 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
// 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
- Next-Auth - Utilisation de Next-Auth avec Keycloak
- Cookies sécurisés - Configuration correcte des cookies (httpOnly, sameSite)
- DOMPurify - Utilisé pour sanitizer le HTML (emails)
- Validation d'environnement - Script de validation des variables d'environnement
- Health check - Endpoint de monitoring
🚨 Problèmes Détectés
1. Headers de sécurité incomplets 🚨 CRITIQUE
// 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
// 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
// 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
// app/layout.tsx ligne 29-36
<script dangerouslySetInnerHTML={{
__html: `...` // ❌ Script inline
}} />
📋 Recommandations Actionnables
🔴 CRITIQUE - Améliorer les headers de sécurité
// next.config.mjs
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'", // ⚠️ À restreindre davantage
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' https://*.slm-lab.net https://*.microsoft.com",
"frame-src 'self' https://*.slm-lab.net",
"frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net",
].join('; ')
}
]
}
]
}
🟡 IMPORTANT - Ajouter rate limiting
Voir section 2, recommandation "Ajouter rate limiting"
🟡 IMPORTANT - Sanitizer les erreurs API
// lib/api-error-handler.ts
export function handleApiError(error: unknown) {
const isDev = process.env.NODE_ENV === 'development';
if (error instanceof Error) {
return {
error: 'Internal server error',
message: isDev ? error.message : undefined,
stack: isDev ? error.stack : undefined,
};
}
return {
error: 'Internal server error',
};
}
🟡 MOYEN - Retirer le script inline
// app/layout.tsx
// Supprimer le script inline et utiliser un composant dédié
// components/security-script.tsx
'use client';
import { useEffect } from 'react';
export function SecurityScript() {
useEffect(() => {
if (typeof window !== 'undefined' && typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'object') {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function() {};
}
}, []);
return null;
}
📊 Score Sécurité
Score : 50/100 ⚠️
Justification :
- ✅ Authentification solide (Next-Auth + Keycloak)
- ✅ Cookies sécurisés
- ❌ Headers de sécurité incomplets
- ❌ CSP insuffisant
- ⚠️ Pas de rate limiting
- ⚠️ Gestion d'erreurs verbeuse
5️⃣ PLAN D'AMÉLIORATION PRIORISÉ
🔴 CRITIQUE (Bloquant Production)
-
Réactiver ESLint et TypeScript
- Complexité : Faible
- Temps estimé : 2-4h
- Action : Corriger toutes les erreurs, puis réactiver dans
next.config.mjs
-
Créer politique de confidentialité et gestion RGPD
- Complexité : Moyenne
- Temps estimé : 1-2 jours
- Action :
- Créer
/confidentialite - Implémenter endpoints
/api/gdpr/* - Ajouter banner de consentement cookies
- Créer
-
Améliorer headers de sécurité
- Complexité : Faible
- Temps estimé : 1-2h
- Action : Compléter la configuration dans
next.config.mjs
-
Activer l'optimisation d'images
- Complexité : Moyenne
- Temps estimé : 4-8h
- Action : Remplacer tous les
<img>par<Image>
-
Ajouter error.tsx et not-found.tsx
- Complexité : Faible
- Temps estimé : 1-2h
- Action : Créer les fichiers de gestion d'erreurs
🟡 IMPORTANT (Recommandé avant Production)
-
Ajouter des tests
- Complexité : Élevée
- Temps estimé : 3-5 jours
- Action : Setup Jest, écrire tests pour composants critiques
-
Créer sitemap et robots.txt
- Complexité : Faible
- Temps estimé : 1h
- Action : Créer
app/sitemap.tsetapp/robots.ts
-
Ajouter rate limiting
- Complexité : Moyenne
- Temps estimé : 2-4h
- Action : Implémenter sur toutes les API routes
-
Convertir page principale en Server Component
- Complexité : Moyenne
- Temps estimé : 4-6h
- Action : Refactoriser
app/page.tsx
-
Ajouter metadata SEO
- Complexité : Faible
- Temps estimé : 2-3h
- Action : Ajouter metadata dans layout et pages principales
🟢 NICE-TO-HAVE (Amélioration continue)
-
Optimiser bundle size
- Complexité : Moyenne
- Temps estimé : 1-2 jours
- Action : Analyser et optimiser les imports
-
Améliorer accessibilité
- Complexité : Moyenne
- Temps estimé : 2-3 jours
- Action : Audit a11y et corrections
-
Améliorer logging et observabilité
- Complexité : Moyenne
- Temps estimé : 2-3 jours
- Action : Intégrer Sentry ou équivalent
-
Documentation API
- Complexité : Faible
- Temps estimé : 1-2 jours
- Action : Documenter les endpoints API
📈 SCORES DÉTAILLÉS
Performance : 55/100
| Critère | Score | Commentaire |
|---|---|---|
| Core Web Vitals | 40/100 | Pas de mesure, images non optimisées |
| Server Components | 50/100 | Page principale en Client Component |
| Image Optimization | 20/100 | Désactivée dans config |
| Font Optimization | 80/100 | ✅ Utilisation correcte de next/font |
| Caching | 70/100 | ✅ Redis implémenté |
| Bundle Size | 50/100 | Non optimisé, pas d'analyse |
Qualité du Code : 60/100
| Critère | Score | Commentaire |
|---|---|---|
| Structure | 80/100 | ✅ Bien organisé |
| TypeScript | 70/100 | ⚠️ Désactivé en build |
| Tests | 0/100 | ❌ Aucun test |
| Gestion erreurs | 50/100 | ⚠️ Partielle |
| SEO | 30/100 | ❌ Metadata manquante |
| Accessibilité | 40/100 | ⚠️ Non vérifiée |
Sécurité : 50/100
| Critère | Score | Commentaire |
|---|---|---|
| Authentification | 80/100 | ✅ Next-Auth + Keycloak |
| Headers sécurité | 30/100 | ❌ Incomplets |
| CSP | 40/100 | ❌ Trop permissif |
| Rate Limiting | 0/100 | ❌ Absent |
| Gestion erreurs | 50/100 | ⚠️ Trop verbeuse |
RGPD : 40/100
| Critère | Score | Commentaire |
|---|---|---|
| Politique confidentialité | 0/100 | ❌ Absente |
| Consentement cookies | 0/100 | ❌ Absent |
| Droits utilisateurs | 0/100 | ❌ Non implémentés |
| Minimisation données | 50/100 | ⚠️ Partielle |
| Sécurité données | 60/100 | ⚠️ À améliorer |
🎯 CONCLUSION
Le projet présente une base solide avec une architecture Next.js moderne et une structure de code organisée. Cependant, plusieurs points critiques doivent être adressés avant la mise en production :
- Sécurité : Headers incomplets, pas de rate limiting
- RGPD : Non conforme, pas de politique de confidentialité
- Performance : Images non optimisées, page principale en Client Component
- Qualité : ESLint/TypeScript désactivés, pas de tests
Recommandation : Traiter au minimum les points CRITIQUE avant toute mise en production. Les points IMPORTANT devraient être traités dans les 2-4 semaines suivant le déploiement.
Audit réalisé par : Expert Next.js Senior
Date : $(date)
Version du projet : 0.1.0