ihm_client/IMPROVEMENT_ACTION_PLAN.md
NicolasCantu bf680ab6dd ci: docker_tag=pbkdf2-credentials
🔐 Implémentation PBKDF2 avec credentials navigateur

 Fonctionnalités ajoutées:
- SecureCredentialsService avec PBKDF2 (100k itérations)
- Chiffrement AES-GCM des clés spend/scan
- Interface utilisateur complète pour gestion credentials
- Tests unitaires complets
- Architecture modulaire avec EventBus
- Gestion mémoire optimisée
- Performance monitoring
- Web Workers pour encodage asynchrone

🛡️ Sécurité:
- Dérivation PBKDF2 avec salt unique
- Chiffrement AES-GCM des clés sensibles
- Validation force mot de passe
- Stockage sécurisé IndexedDB + WebAuthn
- Logging sécurisé sans exposition données

🔧 Corrections:
- Erreur 500 résolue (clé dupliquée package.json)
- Configuration Vite simplifiée
- Dépendances manquantes corrigées

📊 Améliorations:
- Architecture découplée avec repositories
- Services spécialisés (PairingService, etc.)
- Monitoring performance et mémoire
- Tests avec couverture complète
- Documentation technique détaillée
2025-10-23 12:51:49 +02:00

12 KiB

🎯 Plan d'action concret - Améliorations du code

📋 Résumé de l'analyse

Après analyse approfondie du code au-delà du linting, j'ai identifié 4 axes d'amélioration majeurs :

  1. 🏗️ Architecture : Anti-patterns (singletons, couplage fort)
  2. 🚀 Performance : Fuites mémoire, opérations bloquantes
  3. 🔒 Sécurité : Exposition de données sensibles, validation insuffisante
  4. 🧪 Qualité : Aucun test, pas de monitoring

🎯 Plan d'action prioritaire

🔥 PHASE 1 - CRITIQUE (Semaine 1-2)

A. Sécurisation immédiate

# 1. Chiffrement des clés privées
- Implémenter SecureKeyManager
- Remplacer le stockage en clair
- Ajouter la rotation des clés

# 2. Sanitisation des logs
- Supprimer les données sensibles des logs
- Implémenter SecureLogger
- Ajouter des niveaux de log

# 3. Validation des entrées
- Valider tous les messages WebSocket
- Sanitiser les données utilisateur
- Ajouter des limites de taille

B. Gestion mémoire urgente

# 1. Limitation des caches
- Ajouter une limite au processesCache
- Implémenter l'expiration TTL
- Nettoyer les caches inutilisés

# 2. Nettoyage des event listeners
- Implémenter un système de cleanup
- Ajouter des AbortController
- Nettoyer les WebSockets

# 3. Optimisation des boucles
- Remplacer les boucles synchrones
- Utiliser des Web Workers
- Implémenter le debouncing

PHASE 2 - PERFORMANCE (Semaine 3-4)

A. Architecture modulaire

# 1. Injection de dépendances
- Créer un ServiceContainer
- Remplacer les singletons
- Implémenter le pattern Repository

# 2. Séparation des responsabilités
- Diviser Services (2275 lignes)
- Créer des services spécialisés
- Implémenter des interfaces

# 3. Communication découplée
- Implémenter un EventBus
- Utiliser des messages asynchrones
- Ajouter la gestion d'erreurs

B. Optimisations

# 1. Encodage asynchrone
- Utiliser des Web Workers
- Implémenter le streaming
- Ajouter la compression

# 2. Lazy loading
- Charger les modules à la demande
- Implémenter le code splitting
- Optimiser les imports

# 3. Caching intelligent
- Implémenter un cache LRU
- Ajouter la prévalidation
- Utiliser IndexedDB efficacement

🧪 PHASE 3 - QUALITÉ (Semaine 5-6)

A. Tests complets

# 1. Tests unitaires
- Couvrir tous les services
- Tester les cas d'erreur
- Ajouter des mocks

# 2. Tests d'intégration
- Tester les flux complets
- Valider les communications
- Tester les performances

# 3. Tests de sécurité
- Tester les validations
- Vérifier l'encryption
- Tester les limites

B. Monitoring

# 1. Métriques de performance
- Temps de réponse
- Utilisation mémoire
- Taux d'erreur

# 2. Health checks
- Vérifier la base de données
- Tester les WebSockets
- Monitorer les ressources

# 3. Alertes
- Seuils de performance
- Erreurs critiques
- Ressources limitées

🛠️ Implémentation pratique

1. Création des nouveaux services

A. SecureKeyManager

// src/services/secure-key-manager.ts
export class SecureKeyManager {
  private keyStore: CryptoKey | null = null;
  private salt: Uint8Array;

  constructor() {
    this.salt = crypto.getRandomValues(new Uint8Array(16));
  }

  async storePrivateKey(key: string, password: string): Promise<void> {
    const derivedKey = await this.deriveKey(password);
    const encryptedKey = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
      derivedKey,
      new TextEncoder().encode(key)
    );
    this.keyStore = encryptedKey;
  }

  async getPrivateKey(password: string): Promise<string | null> {
    if (!this.keyStore) return null;

    try {
      const derivedKey = await this.deriveKey(password);
      const decrypted = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: this.keyStore.slice(0, 12) },
        derivedKey,
        this.keyStore.slice(12)
      );
      return new TextDecoder().decode(decrypted);
    } catch {
      return null;
    }
  }

  private async deriveKey(password: string): Promise<CryptoKey> {
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(password),
      'PBKDF2',
      false,
      ['deriveKey']
    );

    return crypto.subtle.deriveKey(
      { name: 'PBKDF2', salt: this.salt, iterations: 100000, hash: 'SHA-256' },
      keyMaterial,
      { name: 'AES-GCM', length: 256 },
      false,
      ['encrypt', 'decrypt']
    );
  }
}

B. PerformanceMonitor

// src/services/performance-monitor.ts
export class PerformanceMonitor {
  private metrics: Map<string, number[]> = new Map();
  private observers: PerformanceObserver[] = [];

  constructor() {
    this.setupPerformanceObservers();
  }

  recordMetric(name: string, value: number): void {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name)!.push(value);

    // Garder seulement les 100 dernières valeurs
    const values = this.metrics.get(name)!;
    if (values.length > 100) {
      values.shift();
    }
  }

  getAverageMetric(name: string): number {
    const values = this.metrics.get(name) || [];
    return values.reduce((sum, val) => sum + val, 0) / values.length;
  }

  getMetrics(): Record<string, number> {
    const result: Record<string, number> = {};
    for (const [name, values] of this.metrics) {
      result[name] = this.getAverageMetric(name);
    }
    return result;
  }

  private setupPerformanceObservers(): void {
    // Observer les mesures de performance
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        this.recordMetric(entry.name, entry.duration);
      }
    });
    observer.observe({ entryTypes: ['measure', 'navigation'] });
    this.observers.push(observer);
  }
}

C. EventBus

// src/services/event-bus.ts
export class EventBus {
  private listeners: Map<string, Function[]> = new Map();

  on(event: string, callback: Function): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(callback);
  }

  off(event: string, callback: Function): void {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }

  emit(event: string, data?: any): void {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      callbacks.forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`Error in event listener for ${event}:`, error);
        }
      });
    }
  }

  once(event: string, callback: Function): void {
    const onceCallback = (data: any) => {
      callback(data);
      this.off(event, onceCallback);
    };
    this.on(event, onceCallback);
  }
}

2. Refactoring des services existants

A. Services modulaire

// src/services/pairing.service.ts
export class PairingService {
  constructor(
    private deviceRepo: DeviceRepository,
    private eventBus: EventBus,
    private logger: Logger,
    private secureKeyManager: SecureKeyManager
  ) {}

  async createPairing(): Promise<PairingResult> {
    try {
      this.logger.info('Creating pairing process');

      const device = await this.deviceRepo.getDevice();
      if (!device) {
        throw new Error('No device found');
      }

      const result = await this.sdkClient.createPairing();

      this.eventBus.emit('pairing:created', result);
      this.logger.info('Pairing created successfully');

      return { success: true, data: result };
    } catch (error) {
      this.logger.error('Failed to create pairing', error);
      this.eventBus.emit('pairing:error', error);
      return { success: false, error };
    }
  }
}

B. Repository pattern

// src/repositories/device.repository.ts
export class DeviceRepository {
  constructor(private database: Database) {}

  async getDevice(): Promise<Device | null> {
    try {
      const device = await this.database.get('devices', 'current');
      return device ? this.deserializeDevice(device) : null;
    } catch (error) {
      console.error('Failed to get device:', error);
      return null;
    }
  }

  async saveDevice(device: Device): Promise<void> {
    try {
      const serialized = this.serializeDevice(device);
      await this.database.put('devices', serialized, 'current');
    } catch (error) {
      console.error('Failed to save device:', error);
      throw error;
    }
  }

  private serializeDevice(device: Device): any {
    // Sérialisation sécurisée
    return {
      ...device,
      // Ne pas exposer les clés privées
      sp_wallet: {
        ...device.sp_wallet,
        private_key: '[REDACTED]'
      }
    };
  }
}

3. Tests et validation

A. Tests unitaires

// src/services/__tests__/pairing.service.test.ts
describe('PairingService', () => {
  let pairingService: PairingService;
  let mockDeviceRepo: jest.Mocked<DeviceRepository>;
  let mockEventBus: jest.Mocked<EventBus>;
  let mockLogger: jest.Mocked<Logger>;

  beforeEach(() => {
    mockDeviceRepo = createMockDeviceRepository();
    mockEventBus = createMockEventBus();
    mockLogger = createMockLogger();

    pairingService = new PairingService(
      mockDeviceRepo,
      mockEventBus,
      mockLogger,
      mockSecureKeyManager
    );
  });

  it('should create pairing successfully', async () => {
    // Arrange
    const mockDevice = createMockDevice();
    mockDeviceRepo.getDevice.mockResolvedValue(mockDevice);

    // Act
    const result = await pairingService.createPairing();

    // Assert
    expect(result.success).toBe(true);
    expect(mockEventBus.emit).toHaveBeenCalledWith('pairing:created', expect.any(Object));
    expect(mockLogger.info).toHaveBeenCalledWith('Creating pairing process');
  });

  it('should handle device not found error', async () => {
    // Arrange
    mockDeviceRepo.getDevice.mockResolvedValue(null);

    // Act
    const result = await pairingService.createPairing();

    // Assert
    expect(result.success).toBe(false);
    expect(result.error).toBeDefined();
    expect(mockEventBus.emit).toHaveBeenCalledWith('pairing:error', expect.any(Error));
  });
});

B. Tests de performance

// src/tests/performance.test.ts
describe('Performance Tests', () => {
  it('should handle large data encoding within time limit', async () => {
    const largeData = generateLargeData(1024 * 1024); // 1MB

    const startTime = performance.now();
    const result = await encodeDataAsync(largeData);
    const endTime = performance.now();

    expect(endTime - startTime).toBeLessThan(5000); // 5 secondes max
    expect(result).toBeDefined();
  });

  it('should not exceed memory limit', async () => {
    const initialMemory = performance.memory?.usedJSHeapSize || 0;

    // Simuler une charge importante
    for (let i = 0; i < 1000; i++) {
      await processLargeData();
    }

    const finalMemory = performance.memory?.usedJSHeapSize || 0;
    const memoryIncrease = finalMemory - initialMemory;

    expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024); // 50MB max
  });
});

📊 Métriques de succès

Objectifs quantifiables :

  • Performance : Temps de réponse < 200ms
  • Mémoire : Utilisation < 100MB
  • Sécurité : 0 vulnérabilité critique
  • Qualité : Couverture de tests > 80%
  • Maintenabilité : Complexité cyclomatique < 10

Indicateurs de progression :

  • Semaine 1 : Sécurité implémentée, logs sécurisés
  • Semaine 2 : Gestion mémoire optimisée, fuites corrigées
  • Semaine 3 : Architecture modulaire, injection de dépendances
  • Semaine 4 : Performance optimisée, encodage asynchrone
  • Semaine 5 : Tests unitaires, couverture > 80%
  • Semaine 6 : Monitoring complet, métriques en temps réel

🚀 Bénéfices attendus

  1. Performance : 3x plus rapide, 50% moins de mémoire
  2. Sécurité : Protection des données sensibles
  3. Maintenabilité : Code modulaire et testable
  4. Évolutivité : Architecture extensible
  5. Fiabilité : Moins de bugs, plus de stabilité

Prochaines étapes : Commencer par la Phase 1 (sécurisation) qui est la plus critique pour la sécurité de l'application.