Sécuriser votre Serveur MCP : Permissions, Validation et Protection
Votre serveur MCP expose maintenant plusieurs outils que les IA peuvent découvrir et utiliser. Génial ! Mais une question cruciale se pose : qui peut utiliser quoi ? Dans cet article, nous allons transformer votre serveur en une forteresse sécurisée, sans sacrifier sa simplicité d’utilisation. Parce qu’un serveur puissant doit aussi être un serveur protégé.
Introduction
En 15 ans de développement d’API, j’ai appris une règle d’or : la sécurité ne se rajoute pas après coup, elle se conçoit dès le départ. Un serveur MCP qui donne accès à vos fichiers, vos données, vos ressources sensibles nécessite plusieurs couches de protection. Mais rassurez-vous : sécuriser ne veut pas dire complexifier.
Aujourd’hui, nous allons implémenter quatre piliers de sécurité essentiels : la validation des entrées (pour éviter les données malveillantes), l’authentification (qui êtes-vous ?), l’autorisation (qu’avez-vous le droit de faire ?) et la limitation des ressources (pour éviter les abus). À la fin de cet article, votre serveur sera production-ready.
Les Quatre Piliers de la Sécurité MCP
Avant de coder, comprenons notre stratégie de défense en profondeur :
1. Validation des Entrées
Le principe : Ne jamais faire confiance aux données entrantes. Toujours valider, nettoyer, vérifier.
Pourquoi ? Un paramètre mal validé peut permettre un accès à des fichiers sensibles (../../etc/passwd), une injection de code, ou un crash du serveur.
2. Authentification
Le principe : Identifier qui utilise votre serveur. Chaque requête doit être associée à une identité vérifiée.
Pourquoi ? Sans authentification, n’importe qui peut utiliser vos outils. C’est comme laisser votre maison sans serrure.
3. Autorisation
Le principe : Vérifier les permissions. Même authentifié, tout le monde ne peut pas tout faire.
Pourquoi ? Votre stagiaire n’a pas besoin d’accéder aux fichiers RH. Les permissions granulaires protègent vos données sensibles.
4. Limitation des Ressources
Le principe : Imposer des quotas, des limites de taille, des timeouts.
Pourquoi ? Éviter qu’un utilisateur malveillant (ou une erreur) ne sature votre serveur avec 10 000 requêtes par seconde.
Validation Robuste des Entrées
Commençons par le plus important : valider toutes les entrées. Créez src/security/validator.ts :
<span class="c1">// src/security/validator.ts</span>
<span class="k">import</span> <span class="nx">path</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">InputSchema</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mcp/protocol</span><span class="dl">'</span><span class="p">;</span>
<span class="cm">/**
* Erreur de validation
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">ValidationError</span> <span class="kd">extends</span> <span class="nb">Error</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="k">public</span> <span class="nx">field</span><span class="p">?:</span> <span class="kr">string</span><span class="p">,</span>
<span class="k">public</span> <span class="nx">expected</span><span class="p">?:</span> <span class="kr">string</span>
<span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ValidationError</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Validateur de paramètres basé sur JSON Schema
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">ParameterValidator</span> <span class="p">{</span>
<span class="cm">/**
* Valider les paramètres selon un schéma
*/</span>
<span class="k">static</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">params</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">schema</span><span class="p">:</span> <span class="nx">InputSchema</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="c1">// Vérifier que params est un objet</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">params</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span> <span class="o">||</span> <span class="nx">params</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span><span class="dl">'</span><span class="s1">Les paramètres doivent être un objet</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Vérifier les champs requis</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">requiredField</span> <span class="k">of</span> <span class="nx">schema</span><span class="p">.</span><span class="nx">required</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nx">requiredField</span> <span class="k">in</span> <span class="nx">params</span><span class="p">))</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">requiredField</span><span class="p">}</span><span class="s2">' est requis`</span><span class="p">,</span>
<span class="nx">requiredField</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Valider chaque propriété</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="p">[</span><span class="nx">fieldName</span><span class="p">,</span> <span class="nx">fieldValue</span><span class="p">]</span> <span class="k">of</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">params</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">fieldSchema</span> <span class="o">=</span> <span class="nx">schema</span><span class="p">.</span><span class="nx">properties</span><span class="p">[</span><span class="nx">fieldName</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">fieldSchema</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' n'est pas autorisé`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nx">validateField</span><span class="p">(</span><span class="nx">fieldName</span><span class="p">,</span> <span class="nx">fieldValue</span><span class="p">,</span> <span class="nx">fieldSchema</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Valider un champ spécifique
*/</span>
<span class="k">private</span> <span class="k">static</span> <span class="nx">validateField</span><span class="p">(</span>
<span class="nx">fieldName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">value</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span>
<span class="nx">schema</span><span class="p">:</span> <span class="kr">any</span>
<span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="c1">// Validation de type</span>
<span class="kd">const</span> <span class="nx">actualType</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">value</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">expectedType</span> <span class="o">=</span> <span class="nx">schema</span><span class="p">.</span><span class="kd">type</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expectedType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">actualType</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' doit être une chaîne de caractères`</span><span class="p">,</span>
<span class="nx">fieldName</span><span class="p">,</span>
<span class="nx">expectedType</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expectedType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">actualType</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' doit être un nombre`</span><span class="p">,</span>
<span class="nx">fieldName</span><span class="p">,</span>
<span class="nx">expectedType</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expectedType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">boolean</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">actualType</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">boolean</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' doit être un booléen`</span><span class="p">,</span>
<span class="nx">fieldName</span><span class="p">,</span>
<span class="nx">expectedType</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Validation d'énumération</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="kr">enum</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">schema</span><span class="p">.</span><span class="kr">enum</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' doit être l'une des valeurs : </span><span class="p">${</span><span class="nx">schema</span><span class="p">.</span><span class="kr">enum</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">, </span><span class="dl">'</span><span class="p">)}</span><span class="s2">`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Validation de longueur pour les strings</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expectedType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="nx">minLength</span> <span class="o">&&</span> <span class="nx">value</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="nx">schema</span><span class="p">.</span><span class="nx">minLength</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' doit contenir au moins </span><span class="p">${</span><span class="nx">schema</span><span class="p">.</span><span class="nx">minLength</span><span class="p">}</span><span class="s2"> caractères`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="nx">maxLength</span> <span class="o">&&</span> <span class="nx">value</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="nx">schema</span><span class="p">.</span><span class="nx">maxLength</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' ne peut pas dépasser </span><span class="p">${</span><span class="nx">schema</span><span class="p">.</span><span class="nx">maxLength</span><span class="p">}</span><span class="s2"> caractères`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Validation de plage pour les nombres</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expectedType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="nx">minimum</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&&</span> <span class="nx">value</span> <span class="o"><</span> <span class="nx">schema</span><span class="p">.</span><span class="nx">minimum</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' doit être supérieur ou égal à </span><span class="p">${</span><span class="nx">schema</span><span class="p">.</span><span class="nx">minimum</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="nx">maximum</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&&</span> <span class="nx">value</span> <span class="o">></span> <span class="nx">schema</span><span class="p">.</span><span class="nx">maximum</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' ne peut pas dépasser </span><span class="p">${</span><span class="nx">schema</span><span class="p">.</span><span class="nx">maximum</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Validation de pattern pour les strings</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">expectedType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">schema</span><span class="p">.</span><span class="nx">pattern</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">regex</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="nx">pattern</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">regex</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le champ '</span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2">' ne correspond pas au format attendu`</span><span class="p">,</span>
<span class="nx">fieldName</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Validateur de chemins de fichiers
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PathValidator</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">allowedDirectories</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="k">private</span> <span class="nx">blockedPaths</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">allowedDirectories</span><span class="p">:</span> <span class="kr">string</span><span class="p">[],</span> <span class="nx">blockedPaths</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[])</span> <span class="p">{</span>
<span class="c1">// Résoudre tous les chemins en absolus</span>
<span class="k">this</span><span class="p">.</span><span class="nx">allowedDirectories</span> <span class="o">=</span> <span class="nx">allowedDirectories</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">dir</span> <span class="o">=></span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">dir</span><span class="p">));</span>
<span class="k">this</span><span class="p">.</span><span class="nx">blockedPaths</span> <span class="o">=</span> <span class="nx">blockedPaths</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">p</span> <span class="o">=></span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">p</span><span class="p">));</span>
<span class="p">}</span>
<span class="cm">/**
* Valider qu'un chemin est sûr
*/</span>
<span class="nx">validatePath</span><span class="p">(</span><span class="nx">filePath</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
<span class="c1">// Résoudre le chemin absolu</span>
<span class="kd">const</span> <span class="nx">absolutePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">filePath</span><span class="p">);</span>
<span class="c1">// Vérifier les path traversal (../)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">absolutePath</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">..</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">Les chemins avec ".." ne sont pas autorisés (path traversal)</span><span class="dl">'</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Vérifier que le chemin est dans un répertoire autorisé</span>
<span class="kd">const</span> <span class="nx">isInAllowedDir</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">allowedDirectories</span><span class="p">.</span><span class="nx">some</span><span class="p">(</span><span class="nx">dir</span> <span class="o">=></span>
<span class="nx">absolutePath</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="nx">dir</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isInAllowedDir</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Accès refusé : le chemin doit être dans l'un des répertoires autorisés`</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Vérifier que le chemin n'est pas bloqué</span>
<span class="kd">const</span> <span class="nx">isBlocked</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">blockedPaths</span><span class="p">.</span><span class="nx">some</span><span class="p">(</span><span class="nx">blocked</span> <span class="o">=></span>
<span class="nx">absolutePath</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="nx">blocked</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">isBlocked</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Accès refusé : ce chemin est explicitement bloqué`</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">absolutePath</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Ajouter un répertoire autorisé
*/</span>
<span class="nx">addAllowedDirectory</span><span class="p">(</span><span class="nx">directory</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">allowedDirectories</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">directory</span><span class="p">));</span>
<span class="p">}</span>
<span class="cm">/**
* Bloquer un chemin spécifique
*/</span>
<span class="nx">blockPath</span><span class="p">(</span><span class="nx">pathToBlock</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">blockedPaths</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">pathToBlock</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Validateur de taille de fichier
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">SizeValidator</span> <span class="p">{</span>
<span class="cm">/**
* Valider qu'une taille est acceptable
*/</span>
<span class="k">static</span> <span class="nx">validateSize</span><span class="p">(</span>
<span class="nx">size</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">maxSize</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">fieldName</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">fichier</span><span class="dl">'</span>
<span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">size</span> <span class="o">></span> <span class="nx">maxSize</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">ValidationError</span><span class="p">(</span>
<span class="s2">`Le </span><span class="p">${</span><span class="nx">fieldName</span><span class="p">}</span><span class="s2"> est trop volumineux (max </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">formatSize</span><span class="p">(</span><span class="nx">maxSize</span><span class="p">)}</span><span class="s2">)`</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Formater une taille en octets en format lisible
*/</span>
<span class="k">static</span> <span class="nx">formatSize</span><span class="p">(</span><span class="nx">bytes</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">units</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">octets</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Ko</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Mo</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Go</span><span class="dl">'</span><span class="p">];</span>
<span class="kd">let</span> <span class="nx">size</span> <span class="o">=</span> <span class="nx">bytes</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">unitIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">size</span> <span class="o">>=</span> <span class="mi">1024</span> <span class="o">&&</span> <span class="nx">unitIndex</span> <span class="o"><</span> <span class="nx">units</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">size</span> <span class="o">/=</span> <span class="mi">1024</span><span class="p">;</span>
<span class="nx">unitIndex</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">size</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">)}</span><span class="s2"> </span><span class="p">${</span><span class="nx">units</span><span class="p">[</span><span class="nx">unitIndex</span><span class="p">]}</span><span class="s2">`</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
Ce validateur complet vérifie :
Types de données : string, number, boolean
Valeurs requises : les champs obligatoires sont présents
Énumérations : les valeurs sont dans la liste autorisée
Longueurs : min/max pour les chaînes
Plages : min/max pour les nombres
Patterns : expressions régulières pour les formats
Chemins : protection contre path traversal et accès non autorisés
Tailles : limites de fichiers
Système d’Authentification JWT
Créons maintenant un système d’authentification basé sur JSON Web Tokens. Créez src/security/auth.ts :
<span class="c1">// src/security/auth.ts</span>
<span class="k">import</span> <span class="nx">crypto</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">crypto</span><span class="dl">'</span><span class="p">;</span>
<span class="cm">/**
* Interface utilisateur
*/</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">User</span> <span class="p">{</span>
<span class="nl">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admin</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">user</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">readonly</span><span class="dl">'</span><span class="p">;</span>
<span class="nl">permissions</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="p">}</span>
<span class="cm">/**
* Token JWT simplifié (pour la démo - utilisez une vraie lib JWT en prod)
*/</span>
<span class="kr">interface</span> <span class="nx">Token</span> <span class="p">{</span>
<span class="nl">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">role</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">permissions</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="nl">expiresAt</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Gestionnaire d'authentification
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthManager</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">users</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">User</span><span class="o">></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="k">private</span> <span class="nx">tokens</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">Token</span><span class="o">></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">SECRET_KEY</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">TOKEN_DURATION</span> <span class="o">=</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">;</span> <span class="c1">// 24 heures</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">secretKey</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">SECRET_KEY</span> <span class="o">=</span> <span class="nx">secretKey</span><span class="p">;</span>
<span class="c1">// Créer quelques utilisateurs de test</span>
<span class="k">this</span><span class="p">.</span><span class="nx">createUser</span><span class="p">({</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admin</span><span class="dl">'</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admin</span><span class="dl">'</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">]</span> <span class="c1">// Toutes les permissions</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nx">createUser</span><span class="p">({</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2</span><span class="dl">'</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="dl">'</span><span class="s1">user</span><span class="dl">'</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">user</span><span class="dl">'</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">readFile</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">listFiles</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">searchFiles</span><span class="dl">'</span><span class="p">]</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nx">createUser</span><span class="p">({</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">3</span><span class="dl">'</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="dl">'</span><span class="s1">readonly</span><span class="dl">'</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">readonly</span><span class="dl">'</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">readFile</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">listFiles</span><span class="dl">'</span><span class="p">]</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="cm">/**
* Créer un utilisateur
*/</span>
<span class="nx">createUser</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="nx">User</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span> <span class="nx">user</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Authentifier un utilisateur et générer un token
*/</span>
<span class="nx">authenticate</span><span class="p">(</span><span class="nx">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">string</span> <span class="o">|</span> <span class="kc">null</span> <span class="p">{</span>
<span class="c1">// En production, vérifier le mot de passe hashé !</span>
<span class="c1">// Ici c'est simplifié pour la démo</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">username</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Générer un token</span>
<span class="kd">const</span> <span class="nx">tokenId</span> <span class="o">=</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">randomBytes</span><span class="p">(</span><span class="mi">32</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">hex</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">token</span><span class="p">:</span> <span class="nx">Token</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">userId</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">role</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">,</span>
<span class="na">expiresAt</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">TOKEN_DURATION</span>
<span class="p">};</span>
<span class="k">this</span><span class="p">.</span><span class="nx">tokens</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">,</span> <span class="nx">token</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">tokenId</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Valider un token
*/</span>
<span class="nx">validateToken</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Token</span> <span class="o">|</span> <span class="kc">null</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">tokens</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Vérifier l'expiration</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">></span> <span class="nx">token</span><span class="p">.</span><span class="nx">expiresAt</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">tokens</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">token</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Révoquer un token
*/</span>
<span class="nx">revokeToken</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">tokens</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Obtenir un utilisateur
*/</span>
<span class="nx">getUser</span><span class="p">(</span><span class="nx">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">User</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">username</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Nettoyer les tokens expirés
*/</span>
<span class="nx">cleanExpiredTokens</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="p">[</span><span class="nx">tokenId</span><span class="p">,</span> <span class="nx">token</span><span class="p">]</span> <span class="k">of</span> <span class="k">this</span><span class="p">.</span><span class="nx">tokens</span><span class="p">.</span><span class="nx">entries</span><span class="p">())</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">now</span> <span class="o">></span> <span class="nx">token</span><span class="p">.</span><span class="nx">expiresAt</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">tokens</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Middleware d'authentification pour Express
*/</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">authMiddleware</span><span class="p">(</span><span class="nx">authManager</span><span class="p">:</span> <span class="nx">AuthManager</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">next</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Récupérer le token de l'en-tête Authorization</span>
<span class="kd">const</span> <span class="nx">authHeader</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">authHeader</span> <span class="o">||</span> <span class="o">!</span><span class="nx">authHeader</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">Bearer </span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">401</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Token d</span><span class="se">\'</span><span class="s1">authentification manquant</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">tokenId</span> <span class="o">=</span> <span class="nx">authHeader</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">7</span><span class="p">);</span> <span class="c1">// Enlever "Bearer "</span>
<span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">authManager</span><span class="p">.</span><span class="nx">validateToken</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">401</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Token invalide ou expiré</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">// Ajouter les infos utilisateur à la requête</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">token</span><span class="p">;</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">};</span>
<span class="p">}</span>
Système de Permissions Granulaires
Maintenant, créons un système qui vérifie si un utilisateur peut exécuter un outil spécifique. Créez src/security/permissions.ts :
<span class="c1">// src/security/permissions.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">User</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth</span><span class="dl">'</span><span class="p">;</span>
<span class="cm">/**
* Erreur de permission
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PermissionError</span> <span class="kd">extends</span> <span class="nb">Error</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">PermissionError</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Gestionnaire de permissions
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PermissionManager</span> <span class="p">{</span>
<span class="cm">/**
* Vérifier si un utilisateur a la permission d'utiliser un outil
*/</span>
<span class="k">static</span> <span class="nx">hasPermission</span><span class="p">(</span>
<span class="nx">user</span><span class="p">:</span> <span class="nx">User</span><span class="p">,</span>
<span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">params</span><span class="p">?:</span> <span class="kr">any</span>
<span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
<span class="c1">// Les admins ont accès à tout</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Vérifier la permission spécifique</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">toolName</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Permissions contextuelles supplémentaires</span>
<span class="c1">// Par exemple, vérifier les chemins autorisés pour readFile</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">toolName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">readFile</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">params</span><span class="p">?.</span><span class="nx">chemin_du_fichier</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">canAccessPath</span><span class="p">(</span><span class="nx">user</span><span class="p">,</span> <span class="nx">params</span><span class="p">.</span><span class="nx">chemin_du_fichier</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Vérifier l'accès à un chemin spécifique
*/</span>
<span class="k">private</span> <span class="k">static</span> <span class="nx">canAccessPath</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="nx">User</span><span class="p">,</span> <span class="nx">filePath</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
<span class="c1">// En readonly, uniquement lecture dans certains dossiers</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">role</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">readonly</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">allowedPaths</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">/public</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">/docs</span><span class="dl">'</span><span class="p">];</span>
<span class="k">return</span> <span class="nx">allowedPaths</span><span class="p">.</span><span class="nx">some</span><span class="p">(</span><span class="nx">allowed</span> <span class="o">=></span>
<span class="nx">filePath</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="nx">allowed</span><span class="p">)</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Obtenir les permissions d'un utilisateur
*/</span>
<span class="k">static</span> <span class="nx">getPermissions</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="nx">User</span><span class="p">):</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Vérifier et lancer une erreur si pas de permission
*/</span>
<span class="k">static</span> <span class="nx">requirePermission</span><span class="p">(</span>
<span class="nx">user</span><span class="p">:</span> <span class="nx">User</span><span class="p">,</span>
<span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">params</span><span class="p">?:</span> <span class="kr">any</span>
<span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">hasPermission</span><span class="p">(</span><span class="nx">user</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">,</span> <span class="nx">params</span><span class="p">))</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">PermissionError</span><span class="p">(</span>
<span class="s2">`Permission refusée : vous n'avez pas accès à l'outil '</span><span class="p">${</span><span class="nx">toolName</span><span class="p">}</span><span class="s2">'`</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Politique de permissions pour un outil
*/</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">ToolPolicy</span> <span class="p">{</span>
<span class="nl">allowedRoles</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="nl">requiredPermissions</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="nl">rateLimit</span><span class="p">?:</span> <span class="p">{</span>
<span class="na">maxRequests</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nl">windowMs</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="cm">/**
* Gestionnaire de politiques d'outils
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PolicyManager</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">policies</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">ToolPolicy</span><span class="o">></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="cm">/**
* Définir une politique pour un outil
*/</span>
<span class="nx">setPolicy</span><span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">policy</span><span class="p">:</span> <span class="nx">ToolPolicy</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">policies</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">toolName</span><span class="p">,</span> <span class="nx">policy</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Obtenir la politique d'un outil
*/</span>
<span class="nx">getPolicy</span><span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">ToolPolicy</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">policies</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Vérifier qu'un utilisateur respecte la politique
*/</span>
<span class="nx">checkPolicy</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="nx">User</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">policy</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">policies</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">policy</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// Pas de politique = autorisé par défaut</span>
<span class="p">}</span>
<span class="c1">// Vérifier le rôle</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">policy</span><span class="p">.</span><span class="nx">allowedRoles</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">role</span><span class="p">)</span> <span class="o">&&</span>
<span class="o">!</span><span class="nx">policy</span><span class="p">.</span><span class="nx">allowedRoles</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Vérifier les permissions</span>
<span class="kd">const</span> <span class="nx">hasAllPermissions</span> <span class="o">=</span> <span class="nx">policy</span><span class="p">.</span><span class="nx">requiredPermissions</span><span class="p">.</span><span class="nx">every</span><span class="p">(</span><span class="nx">perm</span> <span class="o">=></span>
<span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">perm</span><span class="p">)</span> <span class="o">||</span> <span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">)</span>
<span class="p">);</span>
<span class="k">return</span> <span class="nx">hasAllPermissions</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
Rate Limiting et Quotas
Protégeons notre serveur contre les abus avec un système de rate limiting. Créez src/security/rateLimit.ts :
<span class="c1">// src/security/rateLimit.ts</span>
<span class="cm">/**
* Enregistrement d'utilisation
*/</span>
<span class="kr">interface</span> <span class="nx">UsageRecord</span> <span class="p">{</span>
<span class="nl">count</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nl">resetAt</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Gestionnaire de rate limiting
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">RateLimiter</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">usage</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">UsageRecord</span><span class="o">></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="k">private</span> <span class="nx">maxRequests</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="k">private</span> <span class="nx">windowMs</span><span class="p">:</span> <span class="kr">number</span>
<span class="p">)</span> <span class="p">{}</span>
<span class="cm">/**
* Vérifier et incrémenter le compteur pour un utilisateur
*/</span>
<span class="nx">checkLimit</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">record</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">usage</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="c1">// Pas d'enregistrement ou fenêtre expirée</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">record</span> <span class="o">||</span> <span class="nx">now</span> <span class="o">></span> <span class="nx">record</span><span class="p">.</span><span class="nx">resetAt</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">usage</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">userId</span><span class="p">,</span> <span class="p">{</span>
<span class="na">count</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="na">resetAt</span><span class="p">:</span> <span class="nx">now</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">windowMs</span>
<span class="p">});</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Limite atteinte</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">record</span><span class="p">.</span><span class="nx">count</span> <span class="o">>=</span> <span class="k">this</span><span class="p">.</span><span class="nx">maxRequests</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Incrémenter le compteur</span>
<span class="nx">record</span><span class="p">.</span><span class="nx">count</span><span class="o">++</span><span class="p">;</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Obtenir les infos de limite pour un utilisateur
*/</span>
<span class="nx">getLimitInfo</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="p">{</span>
<span class="nl">current</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nl">max</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nl">resetsAt</span><span class="p">:</span> <span class="nb">Date</span><span class="p">;</span>
<span class="p">}</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">record</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">usage</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">record</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">current</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="na">max</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">maxRequests</span><span class="p">,</span>
<span class="na">resetsAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">windowMs</span><span class="p">)</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">current</span><span class="p">:</span> <span class="nx">record</span><span class="p">.</span><span class="nx">count</span><span class="p">,</span>
<span class="na">max</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">maxRequests</span><span class="p">,</span>
<span class="na">resetsAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">record</span><span class="p">.</span><span class="nx">resetAt</span><span class="p">)</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="cm">/**
* Réinitialiser le compteur pour un utilisateur
*/</span>
<span class="nx">reset</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">usage</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Nettoyer les enregistrements expirés
*/</span>
<span class="nx">cleanup</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="p">[</span><span class="nx">userId</span><span class="p">,</span> <span class="nx">record</span><span class="p">]</span> <span class="k">of</span> <span class="k">this</span><span class="p">.</span><span class="nx">usage</span><span class="p">.</span><span class="nx">entries</span><span class="p">())</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">now</span> <span class="o">></span> <span class="nx">record</span><span class="p">.</span><span class="nx">resetAt</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">usage</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Middleware de rate limiting pour Express
*/</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">rateLimitMiddleware</span><span class="p">(</span><span class="nx">rateLimiter</span><span class="p">:</span> <span class="nx">RateLimiter</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">next</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">?.</span><span class="nx">userId</span> <span class="o">||</span> <span class="nx">req</span><span class="p">.</span><span class="nx">ip</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">rateLimiter</span><span class="p">.</span><span class="nx">checkLimit</span><span class="p">(</span><span class="nx">userId</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">info</span> <span class="o">=</span> <span class="nx">rateLimiter</span><span class="p">.</span><span class="nx">getLimitInfo</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">429</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Limite de requêtes atteinte</span><span class="dl">'</span><span class="p">,</span>
<span class="na">limit</span><span class="p">:</span> <span class="p">{</span>
<span class="na">max</span><span class="p">:</span> <span class="nx">info</span><span class="p">.</span><span class="nx">max</span><span class="p">,</span>
<span class="na">current</span><span class="p">:</span> <span class="nx">info</span><span class="p">.</span><span class="nx">current</span><span class="p">,</span>
<span class="na">resetsAt</span><span class="p">:</span> <span class="nx">info</span><span class="p">.</span><span class="nx">resetsAt</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="cm">/**
* Gestionnaire de quotas par outil
*/</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">QuotaManager</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">quotas</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="kr">number</span><span class="o">>></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="cm">/**
* Définir un quota pour un utilisateur et un outil
*/</span>
<span class="nx">setQuota</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">maxUsage</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">quotas</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">userId</span><span class="p">))</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">quotas</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">userId</span><span class="p">,</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">());</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nx">quotas</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">userId</span><span class="p">)</span><span class="o">!</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">toolName</span><span class="p">,</span> <span class="nx">maxUsage</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Vérifier et décrémenter le quota
*/</span>
<span class="nx">checkQuota</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">userQuotas</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">quotas</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">userQuotas</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// Pas de quota = illimité</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">remaining</span> <span class="o">=</span> <span class="nx">userQuotas</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">remaining</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// Pas de quota pour cet outil</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">remaining</span> <span class="o"><=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="c1">// Quota épuisé</span>
<span class="p">}</span>
<span class="nx">userQuotas</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">toolName</span><span class="p">,</span> <span class="nx">remaining</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Obtenir le quota restant
*/</span>
<span class="nx">getRemainingQuota</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">null</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">userQuotas</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">quotas</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">userQuotas</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="c1">// Illimité</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">userQuotas</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">toolName</span><span class="p">)</span> <span class="o">||</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Réinitialiser le quota d'un utilisateur
*/</span>
<span class="nx">resetQuota</span><span class="p">(</span><span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">maxUsage</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setQuota</span><span class="p">(</span><span class="nx">userId</span><span class="p">,</span> <span class="nx">toolName</span><span class="p">,</span> <span class="nx">maxUsage</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
Intégration dans le Serveur
Maintenant, intégrons toutes ces couches de sécurité dans notre serveur. Modifiez src/index.ts :
<span class="c1">// src/index.ts</span>
<span class="k">import</span> <span class="nx">express</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">Response</span><span class="p">,</span> <span class="nx">NextFunction</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">MCP_PROTOCOL_VERSION</span><span class="p">,</span> <span class="nx">SERVER_INFO</span><span class="p">,</span> <span class="nx">DiscoveryResponse</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./mcp/protocol</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">toolRegistry</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./mcp/registry</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthManager</span><span class="p">,</span> <span class="nx">authMiddleware</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./security/auth</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PermissionManager</span><span class="p">,</span> <span class="nx">PermissionError</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./security/permissions</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RateLimiter</span><span class="p">,</span> <span class="nx">rateLimitMiddleware</span><span class="p">,</span> <span class="nx">QuotaManager</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./security/rateLimit</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ParameterValidator</span><span class="p">,</span> <span class="nx">ValidationError</span><span class="p">,</span> <span class="nx">PathValidator</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./security/validator</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="c1">// ============================================</span>
<span class="c1">// INITIALISATION SÉCURITÉ</span>
<span class="c1">// ============================================</span>
<span class="kd">const</span> <span class="nx">authManager</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthManager</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">SECRET_KEY</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">your-secret-key-change-me</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">rateLimiter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">RateLimiter</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span> <span class="c1">// 100 req/minute</span>
<span class="kd">const</span> <span class="nx">quotaManager</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">QuotaManager</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">pathValidator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PathValidator</span><span class="p">([</span>
<span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">(),</span> <span class="c1">// Répertoire courant</span>
<span class="dl">'</span><span class="s1">/tmp</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// Dossier temporaire</span>
<span class="p">]);</span>
<span class="c1">// Quotas par défaut</span>
<span class="nx">quotaManager</span><span class="p">.</span><span class="nx">setQuota</span><span class="p">(</span><span class="dl">'</span><span class="s1">2</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">readFile</span><span class="dl">'</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span> <span class="c1">// user : 1000 lectures/jour</span>
<span class="nx">quotaManager</span><span class="p">.</span><span class="nx">setQuota</span><span class="p">(</span><span class="dl">'</span><span class="s1">3</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">readFile</span><span class="dl">'</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span> <span class="c1">// readonly : 100 lectures/jour</span>
<span class="c1">// ============================================</span>
<span class="c1">// MIDDLEWARES</span>
<span class="c1">// ============================================</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
<span class="c1">// Logging</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[</span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">()}</span><span class="s2">] </span><span class="p">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">method</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// ROUTES PUBLIQUES (sans auth)</span>
<span class="c1">// ============================================</span>
<span class="cm">/**
* Page d'accueil
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">MCP File Server - Secured</span><span class="dl">'</span><span class="p">,</span>
<span class="na">version</span><span class="p">:</span> <span class="nx">SERVER_INFO</span><span class="p">.</span><span class="nx">version</span><span class="p">,</span>
<span class="na">protocol_version</span><span class="p">:</span> <span class="nx">MCP_PROTOCOL_VERSION</span><span class="p">,</span>
<span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">operational</span><span class="dl">'</span><span class="p">,</span>
<span class="na">security</span><span class="p">:</span> <span class="p">{</span>
<span class="na">authentication</span><span class="p">:</span> <span class="dl">'</span><span class="s1">required</span><span class="dl">'</span><span class="p">,</span>
<span class="na">rateLimit</span><span class="p">:</span> <span class="dl">'</span><span class="s1">100 requests/minute</span><span class="dl">'</span><span class="p">,</span>
<span class="na">endpoints</span><span class="p">:</span> <span class="p">{</span>
<span class="na">auth</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/auth/login</span><span class="dl">'</span><span class="p">,</span>
<span class="na">discovery</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/mcp/tools</span><span class="dl">'</span><span class="p">,</span>
<span class="na">execute</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/mcp/execute</span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="cm">/**
* Endpoint d'authentification
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/login</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">username</span><span class="p">,</span> <span class="nx">password</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">username</span> <span class="o">||</span> <span class="o">!</span><span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Username et password requis</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">authManager</span><span class="p">.</span><span class="nx">authenticate</span><span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">password</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">401</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Identifiants invalides</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">token</span><span class="p">:</span> <span class="nx">token</span><span class="p">,</span>
<span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Authentification réussie</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="cm">/**
* Health check (public)
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/health</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">healthy</span><span class="dl">'</span><span class="p">,</span>
<span class="na">uptime</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">uptime</span><span class="p">(),</span>
<span class="na">tools_count</span><span class="p">:</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">count</span><span class="p">(),</span>
<span class="na">timestamp</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">()</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// ROUTES PROTÉGÉES (avec auth + rate limit)</span>
<span class="c1">// ============================================</span>
<span class="c1">// Appliquer l'authentification à toutes les routes /mcp/*</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp</span><span class="dl">'</span><span class="p">,</span> <span class="nx">authMiddleware</span><span class="p">(</span><span class="nx">authManager</span><span class="p">));</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp</span><span class="dl">'</span><span class="p">,</span> <span class="nx">rateLimitMiddleware</span><span class="p">(</span><span class="nx">rateLimiter</span><span class="p">));</span>
<span class="cm">/**
* Endpoint de découverte (protégé)
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/tools</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">;</span>
<span class="c1">// Filtrer les outils selon les permissions</span>
<span class="kd">const</span> <span class="nx">allTools</span> <span class="o">=</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">getAllDescriptions</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">allowedTools</span> <span class="o">=</span> <span class="nx">allTools</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">tool</span> <span class="o">=></span>
<span class="nx">PermissionManager</span><span class="p">.</span><span class="nx">hasPermission</span><span class="p">(</span>
<span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">role</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span>
<span class="p">},</span>
<span class="nx">tool</span><span class="p">.</span><span class="nx">name</span>
<span class="p">)</span>
<span class="p">);</span>
<span class="kd">const</span> <span class="na">response</span><span class="p">:</span> <span class="nx">DiscoveryResponse</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">protocol_version</span><span class="p">:</span> <span class="nx">MCP_PROTOCOL_VERSION</span><span class="p">,</span>
<span class="na">server_info</span><span class="p">:</span> <span class="nx">SERVER_INFO</span><span class="p">,</span>
<span class="na">tools</span><span class="p">:</span> <span class="nx">allowedTools</span>
<span class="p">};</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`📋 Découverte - </span><span class="p">${</span><span class="nx">allowedTools</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">allTools</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2"> outils visibles pour </span><span class="p">${</span><span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span>
<span class="p">});</span>
<span class="cm">/**
* Endpoint d'exécution sécurisé
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/execute</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">tool</span><span class="p">,</span> <span class="nx">params</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">;</span>
<span class="k">try</span> <span class="p">{</span>
<span class="c1">// Validation basique</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">tool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Le paramètre 'tool' est requis</span><span class="dl">"</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">// Vérifier les permissions</span>
<span class="nx">PermissionManager</span><span class="p">.</span><span class="nx">requirePermission</span><span class="p">(</span>
<span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">role</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span>
<span class="p">},</span>
<span class="nx">tool</span><span class="p">,</span>
<span class="nx">params</span>
<span class="p">);</span>
<span class="c1">// Vérifier les quotas</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">quotaManager</span><span class="p">.</span><span class="nx">checkQuota</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span> <span class="nx">tool</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Quota épuisé pour cet outil</span><span class="dl">'</span><span class="p">,</span>
<span class="na">remaining</span><span class="p">:</span> <span class="nx">quotaManager</span><span class="p">.</span><span class="nx">getRemainingQuota</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span> <span class="nx">tool</span><span class="p">)</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">// Obtenir la description de l'outil pour validation</span>
<span class="kd">const</span> <span class="nx">toolDescription</span> <span class="o">=</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">getDescription</span><span class="p">(</span><span class="nx">tool</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">toolDescription</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">404</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`Outil '</span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2">' introuvable`</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">// Valider les paramètres selon le schéma</span>
<span class="nx">ParameterValidator</span><span class="p">.</span><span class="nx">validate</span><span class="p">(</span><span class="nx">params</span> <span class="o">||</span> <span class="p">{},</span> <span class="nx">toolDescription</span><span class="p">.</span><span class="nx">input_schema</span><span class="p">);</span>
<span class="c1">// Validation spécifique des chemins pour les outils de fichiers</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tool</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">readFile</span><span class="dl">'</span> <span class="o">||</span> <span class="nx">tool</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">listFiles</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">pathParam</span> <span class="o">=</span> <span class="nx">params</span><span class="p">.</span><span class="nx">chemin_du_fichier</span> <span class="o">||</span> <span class="nx">params</span><span class="p">.</span><span class="nx">chemin_du_dossier</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">pathParam</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">params</span><span class="p">.</span><span class="nx">validated_path</span> <span class="o">=</span> <span class="nx">pathValidator</span><span class="p">.</span><span class="nx">validatePath</span><span class="p">(</span><span class="nx">pathParam</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`⚙️ Exécution sécurisée : </span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2"> par </span><span class="p">${</span><span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="c1">// Exécution via le registre</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="nx">tool</span><span class="p">,</span> <span class="nx">params</span><span class="p">);</span>
<span class="c1">// Log du résultat</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`✅ Succès : </span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2"> par </span><span class="p">${</span><span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`❌ Échec : </span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2"> par </span><span class="p">${</span><span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">result</span><span class="p">.</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="na">error</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Gestion des erreurs de sécurité</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">error</span> <span class="k">instanceof</span> <span class="nx">ValidationError</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">,</span>
<span class="na">field</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">field</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">error</span> <span class="k">instanceof</span> <span class="nx">PermissionError</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">// Erreur générique</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Erreur serveur:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Erreur interne du serveur</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="cm">/**
* Endpoint pour voir ses permissions
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/me</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">;</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">userId</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span>
<span class="na">role</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">role</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">permissions</span><span class="p">,</span>
<span class="na">rateLimit</span><span class="p">:</span> <span class="nx">rateLimiter</span><span class="p">.</span><span class="nx">getLimitInfo</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">)</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="cm">/**
* Endpoint pour voir ses quotas
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/quotas</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">tools</span> <span class="o">=</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">getAllDescriptions</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">quotas</span> <span class="o">=</span> <span class="nx">tools</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">tool</span> <span class="o">=></span> <span class="p">({</span>
<span class="na">tool</span><span class="p">:</span> <span class="nx">tool</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
<span class="na">remaining</span><span class="p">:</span> <span class="nx">quotaManager</span><span class="p">.</span><span class="nx">getRemainingQuota</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span> <span class="nx">tool</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span>
<span class="p">}));</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">userId</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span>
<span class="na">quotas</span><span class="p">:</span> <span class="nx">quotas</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// GESTION DES ERREURS</span>
<span class="c1">// ============================================</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">err</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">,</span> <span class="nx">next</span><span class="p">:</span> <span class="nx">NextFunction</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Erreur non gérée:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Erreur interne du serveur</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// TÂCHES DE MAINTENANCE</span>
<span class="c1">// ============================================</span>
<span class="c1">// Nettoyer les tokens expirés toutes les heures</span>
<span class="nx">setInterval</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">authManager</span><span class="p">.</span><span class="nx">cleanExpiredTokens</span><span class="p">();</span>
<span class="nx">rateLimiter</span><span class="p">.</span><span class="nx">cleanup</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">🧹 Nettoyage des tokens et rate limits expirés</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
<span class="c1">// ============================================</span>
<span class="c1">// DÉMARRAGE DU SERVEUR</span>
<span class="c1">// ============================================</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">🔒 MCP File Server - Secured Edition</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`📍 URL: http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`🔐 Auth: POST http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">/auth/login`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`📋 Découverte: GET http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">/mcp/tools`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`⚙️ Exécution: POST http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">/mcp/execute`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`🔧 Outils: </span><span class="p">${</span><span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">count</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">👤 Utilisateurs de test:</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1"> - admin (toutes permissions)</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1"> - user (lecture + liste + recherche)</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1"> - readonly (lecture + liste seulement)</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
Tester le Système Sécurisé
Relançons notre serveur et testons toutes les couches de sécurité :
npm run dev
Test 1 : Authentification
D’abord, essayons d’accéder sans authentification :
curl http://localhost:3000/mcp/tools
Résultat :
<span class="p">{</span><span class="w">
</span><span class="nl">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Token d'authentification manquant"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Parfait ! Maintenant, authentifions-nous :
curl <span class="nt">-X</span> POST http://localhost:3000/auth/login <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{"username": "user", "password": "any"}'</span>
Réponse :
<span class="p">{</span><span class="w">
</span><span class="nl">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a1b2c3d4e5f6..."</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Authentification réussie"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Sauvegardez ce token dans une variable :
<span class="nv">TOKEN</span><span class="o">=</span><span class="s2">"a1b2c3d4e5f6..."</span>
Test 2 : Découverte avec Permissions
Maintenant, découvrons les outils avec notre token :
curl http://localhost:3000/mcp/tools <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span>
L’utilisateur “user” ne verra que les outils auxquels il a accès (readFile, listFiles, searchFiles).
Comparons avec un utilisateur readonly :
<span class="c"># S'authentifier en readonly</span>
curl <span class="nt">-X</span> POST http://localhost:3000/auth/login <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{"username": "readonly", "password": "any"}'</span> <span class="se">\</span>
| jq <span class="nt">-r</span> <span class="s1">'.token'</span>
<span class="c"># Il verra moins d'outils (seulement readFile et listFiles)</span>
Test 3 : Validation des Paramètres
Testons avec des paramètres invalides :
curl <span class="nt">-X</span> POST http://localhost:3000/mcp/execute <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{
"tool": "readFile",
"params": {
"encoding": "invalid_encoding"
}
}'</span>
Réponse :
<span class="p">{</span><span class="w">
</span><span class="nl">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Le champ 'chemin_du_fichier' est requis"</span><span class="p">,</span><span class="w">
</span><span class="nl">"field"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chemin_du_fichier"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Testons avec une énumération invalide :
curl <span class="nt">-X</span> POST http://localhost:3000/mcp/execute <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{
"tool": "readFile",
"params": {
"chemin_du_fichier": "test.txt",
"encoding": "invalid"
}
}'</span>
Réponse :
<span class="p">{</span><span class="w">
</span><span class="nl">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Le champ 'encoding' doit être l'une des valeurs : utf-8, ascii, base64"</span><span class="p">,</span><span class="w">
</span><span class="nl">"field"</span><span class="p">:</span><span class="w"> </span><span class="s2">"encoding"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Test 4 : Protection Path Traversal
Essayons d’accéder à un fichier sensible :
curl <span class="nt">-X</span> POST http://localhost:3000/mcp/execute <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{
"tool": "readFile",
"params": {
"chemin_du_fichier": "../../../etc/passwd"
}
}'</span>
Réponse :
<span class="p">{</span><span class="w">
</span><span class="nl">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Les chemins avec </span><span class="se">\"</span><span class="s2">..</span><span class="se">\"</span><span class="s2"> ne sont pas autorisés (path traversal)"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Excellent ! Notre protection fonctionne.
Test 5 : Rate Limiting
Créons un script pour tester le rate limiting. Créez test-rate-limit.sh :
<span class="c">#!/bin/bash</span>
<span class="nv">TOKEN</span><span class="o">=</span><span class="s2">"votre-token-ici"</span>
<span class="nb">echo</span> <span class="s2">"Test de rate limiting - 100 requêtes rapides..."</span>
<span class="k">for </span>i <span class="k">in</span> <span class="o">{</span>1..105<span class="o">}</span><span class="p">;</span> <span class="k">do
</span><span class="nv">response</span><span class="o">=</span><span class="si">$(</span>curl <span class="nt">-s</span> <span class="nt">-w</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">%{http_code}"</span> <span class="se">\</span>
<span class="nt">-X</span> POST http://localhost:3000/mcp/execute <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{"tool": "listFiles", "params": {"chemin_du_dossier": "."}}'</span><span class="si">)</span>
<span class="nv">status_code</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$response</span><span class="s2">"</span> | <span class="nb">tail</span> <span class="nt">-n1</span><span class="si">)</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$status_code</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"429"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Requête </span><span class="nv">$i</span><span class="s2"> : Rate limit atteint ! ✓"</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="nv">$response</span><span class="s2">"</span> | <span class="nb">head</span> <span class="nt">-n-1</span> | jq <span class="nb">.</span>
<span class="nb">break
</span><span class="k">else
</span><span class="nb">echo</span> <span class="s2">"Requête </span><span class="nv">$i</span><span class="s2"> : OK"</span>
<span class="k">fi
done</span>
Après 100 requêtes, vous verrez :
<span class="p">{</span><span class="w">
</span><span class="nl">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Limite de requêtes atteinte"</span><span class="p">,</span><span class="w">
</span><span class="nl">"limit"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"max"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
</span><span class="nl">"current"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
</span><span class="nl">"resetsAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-10T15:30:00.000Z"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Test 6 : Quotas
Testons les quotas. Créez un fichier de test puis exécutez plusieurs lectures :
<span class="c"># Voir ses quotas actuels</span>
curl http://localhost:3000/mcp/quotas <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span>
Réponse :
<span class="p">{</span><span class="w">
</span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
</span><span class="nl">"quotas"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"tool"</span><span class="p">:</span><span class="w"> </span><span class="s2">"readFile"</span><span class="p">,</span><span class="w">
</span><span class="nl">"remaining"</span><span class="p">:</span><span class="w"> </span><span class="mi">1000</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"tool"</span><span class="p">:</span><span class="w"> </span><span class="s2">"listFiles"</span><span class="p">,</span><span class="w">
</span><span class="nl">"remaining"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"tool"</span><span class="p">:</span><span class="w"> </span><span class="s2">"searchFiles"</span><span class="p">,</span><span class="w">
</span><span class="nl">"remaining"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Créer un Client Sécurisé
Pour faciliter les tests, créons un client qui gère automatiquement l’authentification. Créez src/secure-client.ts :
<span class="c1">// src/secure-client.ts</span>
<span class="kd">const</span> <span class="nx">SERVER_URL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">class</span> <span class="nx">SecureClient</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">token</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kc">null</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="cm">/**
* S'authentifier
*/</span>
<span class="k">async</span> <span class="nx">login</span><span class="p">(</span><span class="nx">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">boolean</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">SERVER_URL</span><span class="p">}</span><span class="s2">/auth/login`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span>
<span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">username</span><span class="p">,</span> <span class="nx">password</span> <span class="p">})</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">token</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`✅ Authentifié en tant que </span><span class="p">${</span><span class="nx">username</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`❌ Échec d'authentification`</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Découvrir les outils disponibles
*/</span>
<span class="k">async</span> <span class="nx">discoverTools</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Non authentifié</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">SERVER_URL</span><span class="p">}</span><span class="s2">/mcp/tools`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">return</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="p">}</span>
<span class="cm">/**
* Exécuter un outil
*/</span>
<span class="k">async</span> <span class="nx">executeTool</span><span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">params</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Non authentifié</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">SERVER_URL</span><span class="p">}</span><span class="s2">/mcp/execute`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span>
<span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
<span class="na">tool</span><span class="p">:</span> <span class="nx">toolName</span><span class="p">,</span>
<span class="na">params</span><span class="p">:</span> <span class="nx">params</span>
<span class="p">})</span>
<span class="p">});</span>
<span class="k">return</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="p">}</span>
<span class="cm">/**
* Voir ses informations
*/</span>
<span class="k">async</span> <span class="nx">getMe</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Non authentifié</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">SERVER_URL</span><span class="p">}</span><span class="s2">/mcp/me`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">return</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="p">}</span>
<span class="cm">/**
* Voir ses quotas
*/</span>
<span class="k">async</span> <span class="nx">getQuotas</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Non authentifié</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">SERVER_URL</span><span class="p">}</span><span class="s2">/mcp/quotas`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">return</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Démonstration complète
*/</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">demo</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">🔒 Démo Client Sécurisé MCP</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SecureClient</span><span class="p">();</span>
<span class="c1">// Test 1 : Authentification</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">📝 Test 1 : Authentification</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">user</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">password</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// Test 2 : Voir ses infos</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="s1">📝 Test 2 : Informations utilisateur</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">me</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">getMe</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">me</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
<span class="c1">// Test 3 : Découverte des outils</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="s1">📝 Test 3 : Découverte des outils</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">discovery</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">discoverTools</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Outils disponibles : </span><span class="p">${</span><span class="nx">discovery</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">discovery</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">tool</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">` - </span><span class="p">${</span><span class="nx">tool</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">: </span><span class="p">${</span><span class="nx">tool</span><span class="p">.</span><span class="nx">description</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// Test 4 : Exécution d'un outil</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="s1">📝 Test 4 : Exécution d</span><span class="se">\'</span><span class="s1">un outil</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">executeTool</span><span class="p">(</span><span class="dl">'</span><span class="s1">listFiles</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">chemin_du_dossier</span><span class="p">:</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span>
<span class="p">});</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">✅ Succès !</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`❌ Erreur : </span><span class="p">${</span><span class="nx">result</span><span class="p">.</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Test 5 : Vérifier les quotas</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="s1">📝 Test 5 : Quotas restants</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">quotas</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">getQuotas</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">quotas</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">demo</span><span class="p">().</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">);</span>
Ajoutez le script dans package.json :
<span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ts-node src/index.ts"</span><span class="p">,</span><span class="w">
</span><span class="nl">"secure-client"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ts-node src/secure-client.ts"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Lancez-le :
npm run secure-client
Dashboard de Sécurité
Créons une interface web pour visualiser la sécurité. Créez src/public/security.html :
<span class="cp"></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"fr"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">></span>
<span class="nt"><title></span>MCP Security Dashboard<span class="nt"></title></span>
<span class="nt"><style></span>
<span class="o">*</span> <span class="p">{</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="nl">box-sizing</span><span class="p">:</span> <span class="n">border-box</span><span class="p">;</span> <span class="p">}</span>
<span class="nt">body</span> <span class="p">{</span>
<span class="nl">font-family</span><span class="p">:</span> <span class="s2">'Segoe UI'</span><span class="p">,</span> <span class="n">system-ui</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span><span class="m">135deg</span><span class="p">,</span> <span class="m">#1e3a8a</span> <span class="m">0%</span><span class="p">,</span> <span class="m">#ef4444</span> <span class="m">100%</span><span class="p">);</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.container</span> <span class="p">{</span>
<span class="nl">max-width</span><span class="p">:</span> <span class="m">1200px</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span> <span class="nb">auto</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">12px</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">30px</span><span class="p">;</span>
<span class="nl">box-shadow</span><span class="p">:</span> <span class="m">0</span> <span class="m">20px</span> <span class="m">60px</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0.3</span><span class="p">);</span>
<span class="p">}</span>
<span class="nt">h1</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="m">#1e3a8a</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
<span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
<span class="py">gap</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.subtitle</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="m">#64748b</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">30px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.login-form</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#f8fafc</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.login-form</span> <span class="nt">input</span> <span class="p">{</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">200px</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">8px</span> <span class="m">12px</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#cbd5e1</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.login-form</span> <span class="nt">button</span> <span class="p">{</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">8px</span> <span class="m">16px</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#1e3a8a</span><span class="p">;</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
<span class="nl">cursor</span><span class="p">:</span> <span class="nb">pointer</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.login-form</span> <span class="nt">button</span><span class="nd">:hover</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#1e40af</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.user-info</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#eff6ff</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.security-grid</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
<span class="py">grid-template-columns</span><span class="p">:</span> <span class="nb">repeat</span><span class="p">(</span><span class="n">auto-fit</span><span class="p">,</span> <span class="n">minmax</span><span class="p">(</span><span class="m">300px</span><span class="p">,</span> <span class="m">1</span><span class="n">fr</span><span class="p">));</span>
<span class="py">gap</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">margin-top</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.security-card</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#f8fafc</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#e2e8f0</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.security-card</span> <span class="nt">h3</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="m">#1e3a8a</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">15px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.permission-badge</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">4px</span> <span class="m">8px</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#3b82f6</span><span class="p">;</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="m">0.85em</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">2px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.quota-bar</span> <span class="p">{</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#e2e8f0</span><span class="p">;</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
<span class="nl">overflow</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">10px</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.quota-fill</span> <span class="p">{</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span><span class="m">90deg</span><span class="p">,</span> <span class="m">#3b82f6</span><span class="p">,</span> <span class="m">#8b5cf6</span><span class="p">);</span>
<span class="nl">transition</span><span class="p">:</span> <span class="n">width</span> <span class="m">0.3s</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.hidden</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.error</span> <span class="p">{</span> <span class="nl">color</span><span class="p">:</span> <span class="m">#ef4444</span><span class="p">;</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span> <span class="nl">background</span><span class="p">:</span> <span class="m">#fee2e2</span><span class="p">;</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span> <span class="p">}</span>
<span class="nt"></style></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">></span>
<span class="nt"><h1></span>🔒 MCP Security Dashboard<span class="nt"></h1></span>
<span class="nt"><p</span> <span class="na">class=</span><span class="s">"subtitle"</span><span class="nt">></span>Gestion de la sécurité et des permissions<span class="nt"></p></span>
<span class="c"><!-- Formulaire de connexion --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"loginForm"</span> <span class="na">class=</span><span class="s">"login-form"</span><span class="nt">></span>
<span class="nt"><h3></span>Connexion<span class="nt"></h3></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"username"</span> <span class="na">placeholder=</span><span class="s">"Username"</span> <span class="na">value=</span><span class="s">"user"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"password"</span> <span class="na">id=</span><span class="s">"password"</span> <span class="na">placeholder=</span><span class="s">"Password"</span> <span class="na">value=</span><span class="s">"any"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">onclick=</span><span class="s">"login()"</span><span class="nt">></span>Se connecter<span class="nt"></button></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"loginError"</span> <span class="na">class=</span><span class="s">"error hidden"</span><span class="nt">></div></span>
<span class="nt"></div></span>
<span class="c"><!-- Informations utilisateur --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"userInfo"</span> <span class="na">class=</span><span class="s">"hidden user-info"</span><span class="nt">></span>
<span class="nt"><h3></span>👤 Utilisateur connecté<span class="nt"></h3></span>
<span class="nt"><p><strong></span>Username:<span class="nt"></strong></span> <span class="nt"><span</span> <span class="na">id=</span><span class="s">"userUsername"</span><span class="nt">></span></p></span>
<span class="nt"><p><strong></span>Rôle:<span class="nt"></strong></span> <span class="nt"><span</span> <span class="na">id=</span><span class="s">"userRole"</span><span class="nt">></span></p></span>
<span class="nt"><p><strong></span>User ID:<span class="nt"></strong></span> <span class="nt"><span</span> <span class="na">id=</span><span class="s">"userId"</span><span class="nt">></span></p></span>
<span class="nt"><button</span> <span class="na">onclick=</span><span class="s">"logout()"</span> <span class="na">style=</span><span class="s">"margin-top: 10px;"</span><span class="nt">></span>Déconnexion<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="c"><!-- Grille de sécurité --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"securityGrid"</span> <span class="na">class=</span><span class="s">"hidden security-grid"</span><span class="nt">></span>
<span class="c"><!-- Permissions --></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"security-card"</span><span class="nt">></span>
<span class="nt"><h3></span>🔑 Permissions<span class="nt"></h3></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"permissions"</span><span class="nt">></div></span>
<span class="nt"></div></span>
<span class="c"><!-- Rate Limit --></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"security-card"</span><span class="nt">></span>
<span class="nt"><h3></span>⏱️ Rate Limiting<span class="nt"></h3></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"rateLimit"</span><span class="nt">></div></span>
<span class="nt"></div></span>
<span class="c"><!-- Quotas --></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"security-card"</span><span class="nt">></span>
<span class="nt"><h3></span>📊 Quotas<span class="nt"></h3></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"quotas"</span><span class="nt">></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><script></span>
<span class="kd">let</span> <span class="nx">token</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">login</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">username</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">username</span><span class="dl">'</span><span class="p">).</span><span class="nx">value</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">password</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">password</span><span class="dl">'</span><span class="p">).</span><span class="nx">value</span><span class="p">;</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/login</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span> <span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">username</span><span class="p">,</span> <span class="nx">password</span> <span class="p">})</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">token</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">token</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">loginForm</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">securityGrid</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">loadSecurityData</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">showError</span><span class="p">(</span><span class="dl">'</span><span class="s1">Identifiants invalides</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">showError</span><span class="p">(</span><span class="dl">'</span><span class="s1">Erreur de connexion</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">logout</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">token</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">loginForm</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">securityGrid</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">showError</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">errorDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">loginError</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">errorDiv</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">message</span><span class="p">;</span>
<span class="nx">errorDiv</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">errorDiv</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">),</span> <span class="mi">3000</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">loadSecurityData</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Charger les infos utilisateur</span>
<span class="kd">const</span> <span class="nx">meResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/me</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">}</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">me</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">meResponse</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">userUsername</span><span class="dl">'</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">userRole</span><span class="dl">'</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">role</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">userId</span><span class="dl">'</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">userId</span><span class="p">;</span>
<span class="c1">// Afficher les permissions</span>
<span class="kd">const</span> <span class="nx">permissionsDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">permissions</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">permissionsDiv</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">permissions</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">p</span> <span class="o">=></span>
<span class="s2">`<span class="permission-badge"></span><span class="p">${</span><span class="nx">p</span><span class="p">}</span><span class="s2"></span>`</span>
<span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
<span class="c1">// Afficher le rate limit</span>
<span class="kd">const</span> <span class="nx">rateLimitDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">rateLimit</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">rateLimit</span> <span class="o">=</span> <span class="nx">me</span><span class="p">.</span><span class="nx">rateLimit</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rateLimit</span><span class="p">.</span><span class="nx">current</span> <span class="o">/</span> <span class="nx">rateLimit</span><span class="p">.</span><span class="nx">max</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">;</span>
<span class="nx">rateLimitDiv</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`
<p><strong></span><span class="p">${</span><span class="nx">rateLimit</span><span class="p">.</span><span class="nx">current</span><span class="p">}</span><span class="s2"></strong> / </span><span class="p">${</span><span class="nx">rateLimit</span><span class="p">.</span><span class="nx">max</span><span class="p">}</span><span class="s2"> requêtes</p>
<div class="quota-bar">
<div class="quota-fill" style="width: </span><span class="p">${</span><span class="nx">percentage</span><span class="p">}</span><span class="s2">%"></div>
</div>
<p style="font-size: 0.9em; color: #64748b;">
Reset: </span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">rateLimit</span><span class="p">.</span><span class="nx">resetsAt</span><span class="p">).</span><span class="nx">toLocaleTimeString</span><span class="p">()}</span><span class="s2">
</p>
`</span><span class="p">;</span>
<span class="c1">// Charger les quotas</span>
<span class="kd">const</span> <span class="nx">quotasResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/quotas</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">}</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">quotasData</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">quotasResponse</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">quotasDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">quotas</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">quotasDiv</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">quotasData</span><span class="p">.</span><span class="nx">quotas</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">q</span> <span class="o">=></span> <span class="s2">`
<p><strong></span><span class="p">${</span><span class="nx">q</span><span class="p">.</span><span class="nx">tool</span><span class="p">}</span><span class="s2">:</strong> </span><span class="p">${</span><span class="nx">q</span><span class="p">.</span><span class="nx">remaining</span> <span class="o">===</span> <span class="kc">null</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">Illimité</span><span class="dl">'</span> <span class="p">:</span> <span class="nx">q</span><span class="p">.</span><span class="nx">remaining</span><span class="p">}</span><span class="s2"></p>
`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
Ouvrez http://localhost:3000/security.html pour voir le dashboard en action !
Bonnes Pratiques de Sécurité
Maintenant que vous avez un système complet, voici les bonnes pratiques à suivre :
1. Gestion des Secrets
Ne jamais commit les secrets dans le code. Utilisez des variables d’environnement :
<span class="c1">// Créez un fichier .env</span>
<span class="nx">SECRET_KEY</span><span class="o">=</span><span class="nx">your</span><span class="o">-</span><span class="k">super</span><span class="o">-</span><span class="nx">secret</span><span class="o">-</span><span class="nx">key</span><span class="o">-</span><span class="nx">change</span><span class="o">-</span><span class="nx">me</span><span class="o">-</span><span class="k">in</span><span class="o">-</span><span class="nx">production</span>
<span class="nx">JWT_EXPIRATION</span><span class="o">=</span><span class="mi">86400</span>
<span class="c1">// Dans votre code</span>
<span class="k">import</span> <span class="nx">dotenv</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">dotenv</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">dotenv</span><span class="p">.</span><span class="nx">config</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">SECRET_KEY</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">SECRET_KEY</span><span class="p">;</span>
2. Hash des Mots de Passe
En production, utilisez bcrypt pour hasher les mots de passe :
npm <span class="nb">install </span>bcrypt @types/bcrypt
<span class="k">import</span> <span class="nx">bcrypt</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">bcrypt</span><span class="dl">'</span><span class="p">;</span>
<span class="c1">// Lors de la création d'un utilisateur</span>
<span class="kd">const</span> <span class="nx">hashedPassword</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">hash</span><span class="p">(</span><span class="nx">password</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="c1">// Lors de l'authentification</span>
<span class="kd">const</span> <span class="nx">isValid</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">compare</span><span class="p">(</span><span class="nx">password</span><span class="p">,</span> <span class="nx">user</span><span class="p">.</span><span class="nx">hashedPassword</span><span class="p">);</span>
3. HTTPS Obligatoire
En production, forcez HTTPS :
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="dl">'</span><span class="s1">x-forwarded-proto</span><span class="dl">'</span><span class="p">)</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">https</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">production</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="s2">`https://</span><span class="p">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="dl">'</span><span class="s1">host</span><span class="dl">'</span><span class="p">)}${</span><span class="nx">req</span><span class="p">.</span><span class="nx">url</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
4. Logging de Sécurité
Logguez tous les événements de sécurité :
<span class="kd">function</span> <span class="nx">logSecurityEvent</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">details</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// En production : envoyer vers un service d'audit</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
<span class="na">timestamp</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
<span class="na">event</span><span class="p">:</span> <span class="nx">event</span><span class="p">,</span>
<span class="p">...</span><span class="nx">details</span>
<span class="p">}));</span>
<span class="p">}</span>
<span class="c1">// Utilisation</span>
<span class="nx">logSecurityEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">AUTH_FAILED</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">username</span><span class="p">,</span> <span class="na">ip</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">ip</span> <span class="p">});</span>
<span class="nx">logSecurityEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">PERMISSION_DENIED</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">user</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span> <span class="nx">tool</span> <span class="p">});</span>
5. Audits Réguliers
Créez un système d’audit :
<span class="kd">class</span> <span class="nx">AuditLogger</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">(</span><span class="nx">action</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">details</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// En production : envoyer vers un service d'audit</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span>
<span class="na">timestamp</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
<span class="nx">action</span><span class="p">,</span>
<span class="nx">userId</span><span class="p">,</span>
<span class="p">...</span><span class="nx">details</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
Conclusion
Félicitations ! Vous avez maintenant un serveur MCP production-ready avec quatre couches de sécurité :
- ✅ Validation complète des entrées
- ✅ Authentification par tokens
- ✅ Autorisation granulaire
- ✅ Rate limiting et quotas
Votre serveur peut maintenant être exposé en production en toute confiance. Les IA peuvent l’utiliser de manière sécurisée, chaque utilisateur a ses permissions spécifiques, et les abus sont automatiquement bloqués.
Dans le prochain et dernier article de la série, nous allons connecter votre serveur sécurisé à Claude Desktop et tester l’intégration complète en conditions réelles. Vous verrez enfin tout le système fonctionner de bout en bout avec une véritable IA.
En attendant, testez votre système de sécurité ! Essayez de le contourner, testez les limites, vérifiez que tout est bien protégé. Un bon système de sécurité est un système qui a été attaqué et qui a résisté.
Article publié le 10 décembre 2025 par Nicolas Dabène - Expert PHP & PrestaShop avec 15+ ans d’expérience dans l’architecture logicielle et l’intégration d’IA
À lire aussi :
- Comprendre le Model Context Protocol (MCP) : Une Conversation Simple
- Créer son Premier Serveur MCP : Setup du Projet TypeScript
- Créer votre Premier Outil MCP : L’Outil readFile Expliqué
- Le Menu MCP : Comment l’IA Découvre et Utilise vos Outils
- Connecter votre Serveur MCP à Claude Desktop : L’Intégration Complète