🔐 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
474 lines
12 KiB
Markdown
474 lines
12 KiB
Markdown
# 🎯 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**
|
|
```bash
|
|
# 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**
|
|
```bash
|
|
# 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**
|
|
```bash
|
|
# 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**
|
|
```bash
|
|
# 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**
|
|
```bash
|
|
# 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**
|
|
```bash
|
|
# 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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
// 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.
|