ihm_client/src/pages/wallet-setup/wallet-setup.ts

471 lines
24 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Page de génération du wallet
* Deuxième étape du processus d'initialisation
*/
import { DATABASE_CONFIG } from '../../services/database-config';
document.addEventListener('DOMContentLoaded', async () => {
console.log('💰 Wallet setup page loaded');
const status = document.getElementById('status') as HTMLDivElement;
const progressBar = document.getElementById('progressBar') as HTMLDivElement;
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
status.textContent = message;
status.className = `status ${type}`;
}
function updateProgress(percent: number) {
progressBar.style.width = `${percent}%`;
}
// Méthode pour sauvegarder directement en IndexedDB dans la base 4nk
async function saveCredentialsDirectly(credentials: any): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite');
const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
const putRequest = store.put(credentials, '/4nk/credentials');
putRequest.onsuccess = () => resolve();
putRequest.onerror = () => reject(putRequest.error);
};
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
}
};
});
}
try {
// Étape 1: Initialisation des services
updateStatus('🔄 Initialisation des services...', 'loading');
updateProgress(10);
let services: any; // Déclarer services au niveau supérieur
try {
console.log('🔄 Importing services...');
const serviceModule = await import('../../services/service');
console.log('✅ Service module imported:', Object.keys(serviceModule));
// La classe Services est exportée par défaut
const Services = serviceModule.default;
if (!Services) {
throw new Error('Services class not found in default export');
}
console.log('🔄 Waiting for services to be ready...');
// Attendre que les services soient initialisés avec plus de patience
let attempts = 0;
const maxAttempts = 30; // Plus de tentatives
const delayMs = 2000; // Délai plus long entre les tentatives
while (attempts < maxAttempts) {
try {
console.log(`🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...`);
services = await Services.getInstance();
console.log('✅ Services initialized successfully');
break;
} catch (error) {
console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, error instanceof Error ? error.message : String(error));
// Diagnostic plus détaillé
if (attempts === 5) {
console.log('🔍 Diagnostic: Checking memory usage...');
if ((performance as any).memory) {
const memory = (performance as any).memory;
console.log(`📊 Memory usage: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB / ${Math.round(memory.totalJSHeapSize / 1024 / 1024)}MB`);
}
}
attempts++;
if (attempts >= maxAttempts) {
throw new Error(`Services failed to initialize after ${maxAttempts} attempts. This may be due to high memory usage or WebAssembly initialization issues.`);
}
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
} catch (error) {
console.error('❌ Services not available:', error);
throw error;
}
// Étape 2: Génération des credentials sécurisés
updateStatus('🔐 Génération des clés de sécurité...', 'loading');
updateProgress(30);
try {
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Vérifier si des credentials existent déjà
// CRITICAL: wallet-setup ne doit PAS générer de credentials
// Les credentials et clé PBKDF2 doivent être créés dans security-setup
// On vérifie juste que la clé PBKDF2 existe
console.log('🔐 Checking for existing PBKDF2 key...');
} catch (error) {
// Erreur lors de la vérification, on continuera pour tester les clés PBKDF2
console.log(' Could not check credentials status, will test PBKDF2 keys directly');
}
// Étape 3: Sauvegarde du wallet avec état birthday_waiting
updateStatus('💰 Sauvegarde du portefeuille...', 'loading');
updateProgress(60);
try {
console.log('🔐 Sauvegarde du wallet avec état birthday_waiting...');
// DÉTECTER le mode de sécurité via la clé PBKDF2 disponible
// Le mode de sécurité est identifié par la clé PBKDF2 qui fonctionne
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// TEST: Log credentialId from sessionStorage
const storedCredentialId = sessionStorage.getItem('webauthn_credential_id');
if (storedCredentialId) {
console.log('🔐 TEST: credentialId from sessionStorage in wallet-setup:', storedCredentialId);
} else {
console.log('🔐 TEST: No credentialId found in sessionStorage');
}
console.log('🔍 Testing all security modes to find a PBKDF2 key...');
// Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide
const allSecurityModes: Array<'browser' | 'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
['browser', 'otp', 'password', 'none', 'os', 'proton-pass'];
let currentMode: string | null = null;
for (const mode of allSecurityModes) {
console.log(`🔍 Testing security mode: ${mode}`);
try {
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
if (key) {
currentMode = mode;
console.log(`✅ PBKDF2 key found for security mode: ${mode}`);
break;
}
} catch (error) {
console.log(`⚠️ No PBKDF2 key found for mode ${mode}`);
}
}
// CRITICAL: Si aucune clé PBKDF2 n'est disponible, arrêter immédiatement
if (!currentMode) {
console.error('❌ CRITICAL: No PBKDF2 key found for any security mode.');
console.error('❌ Cannot proceed with wallet creation without encryption key.');
updateStatus('❌ Erreur: Aucune clé de chiffrement trouvée. Veuillez configurer la sécurité.', 'error');
throw new Error('CRITICAL: No PBKDF2 key found. Cannot create wallet without encryption key.');
}
console.log('🔐 Using security mode for wallet encryption:', currentMode);
// Générer un wallet temporaire avec état birthday_waiting
const { EncryptionService } = await import('../../services/encryption.service');
const encryptionService = EncryptionService.getInstance();
// Générer des clés temporaires pour le wallet
// Générer un wallet temporaire avec l'état birthday_waiting
const walletData = {
scan_sk: encryptionService.generateRandomKey(),
spend_key: encryptionService.generateRandomKey(),
network: 'signet',
state: 'birthday_waiting',
created_at: new Date().toISOString()
};
console.log('🔐 Wallet data generated:', walletData);
// Récupérer la clé PBKDF2 existante pour le mode détecté
// IMPORTANT: Ne PAS générer de nouvelle clé, utiliser celle qui existe
console.log('🔐 Retrieving existing PBKDF2 key for security mode:', currentMode);
const pbkdf2Key = await secureCredentialsService.retrievePBKDF2Key(currentMode as any);
if (!pbkdf2Key) {
console.error('❌ CRITICAL: Failed to retrieve PBKDF2 key for mode:', currentMode);
updateStatus('❌ Erreur: Impossible de récupérer la clé de chiffrement.', 'error');
throw new Error('CRITICAL: Failed to retrieve PBKDF2 key');
}
console.log('🔐 PBKDF2 key retrieved for wallet encryption');
// Chiffrer le wallet avec la clé PBKDF2
const encryptedWallet = await encryptionService.encrypt(
JSON.stringify(walletData),
pbkdf2Key
);
console.log('🔐 Wallet encrypted with PBKDF2 key');
console.log('🔐 Encrypted wallet data:', encryptedWallet);
// Ouvrir la base de données 4nk existante sans la modifier
console.log(`🔍 Opening IndexedDB database "${DATABASE_CONFIG.name}" version ${DATABASE_CONFIG.version}...`);
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); // Utiliser la version centralisée
request.onerror = () => {
console.error('❌ Failed to open IndexedDB:', request.error);
reject(request.error);
};
request.onsuccess = () => {
console.log('✅ IndexedDB opened successfully');
console.log('🔍 Database name:', request.result.name);
console.log('🔍 Database version:', request.result.version);
console.log('🔍 Available stores:', Array.from(request.result.objectStoreNames));
resolve(request.result);
};
request.onupgradeneeded = () => {
const db = request.result;
console.log('🔄 IndexedDB upgrade needed, checking wallet store...');
// Créer le store wallet seulement s'il n'existe pas
if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
const store = db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
console.log(`✅ Wallet store created with keyPath: ${DATABASE_CONFIG.stores.wallet.keyPath}`);
} else {
console.log('✅ Wallet store already exists');
}
};
});
// Étape 1: Sauvegarder le wallet dans le format attendu par getDeviceFromDatabase
// Utiliser le SDK directement pour éviter le service worker qui bloque
console.log('🔧 Creating device using SDK...');
// Créer le device directement avec le SDK sans passer par saveDeviceInDatabase
if (!services.sdkClient) {
throw new Error('WebAssembly SDK not initialized');
}
// We set birthday later when we have the chain tip from relay
console.log('🔧 Creating new device with birthday 0...');
const spAddress = await services.sdkClient.create_new_device(0, 'signet');
console.log('✅ Device created with address:', spAddress);
// Force wallet generation to ensure keys are created
console.log('🔧 Forcing wallet generation...');
try {
const wallet = await services.sdkClient.dump_wallet();
console.log('✅ Wallet generated:', JSON.stringify(wallet));
} catch (walletError) {
console.warn('⚠️ Wallet generation failed:', walletError);
}
// Récupérer le device créé par le SDK
const device = services.dumpDeviceFromMemory();
console.log('🔍 Device structure from SDK:', {
hasSpWallet: !!device.sp_wallet,
hasSpClient: !!device.sp_client,
spClientKeys: device.sp_client ? Object.keys(device.sp_client) : 'none'
});
console.log(`🔍 Opening transaction for ${DATABASE_CONFIG.stores.wallet.name} store...`);
// CRITICAL: Chiffrer TOUS les données du wallet avant stockage
// Le device contient des données sensibles (sp_wallet) qui ne doivent JAMAIS être en clair
console.log('🔐 Encrypting device data before storage...');
const deviceString = JSON.stringify(device);
const encryptedDevice = await encryptionService.encrypt(deviceString, pbkdf2Key);
console.log('🔐 Device encrypted successfully');
await new Promise<void>((resolve, reject) => {
const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite');
const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
console.log('🔍 Store opened:', store.name);
// Stocker UNIQUEMENT des données chiffrées - aucun wallet en clair
const walletObject = {
pre_id: '1',
encrypted_device: encryptedDevice, // Device complètement chiffré
encrypted_wallet: encryptedWallet, // Wallet chiffré
security_mode: currentMode // Mode de sécurité utilisé
};
console.log('🔍 Attempting to save encrypted wallet object');
console.log('🔐 Object contains only encrypted data:', {
hasEncryptedDevice: !!walletObject.encrypted_device,
hasEncryptedWallet: !!walletObject.encrypted_wallet,
securityMode: walletObject.security_mode,
// Ne pas logger le contenu chiffré
});
// Le store utilise des clés in-line (keyPath: 'pre_id'), ne pas fournir de clé explicite
const request = store.put(walletObject);
request.onsuccess = () => {
console.log('✅ Wallet saved in IndexedDB with correct format');
console.log('🔍 Saved wallet object:', walletObject);
resolve();
};
request.onerror = () => {
console.error('❌ Failed to save wallet in IndexedDB:', request.error);
reject(request.error);
};
transaction.oncomplete = () => {
console.log('✅ Transaction completed successfully');
console.log('🔍 Transaction completed for store:', store.name);
};
transaction.onerror = () => {
console.error('❌ Transaction failed:', transaction.error);
console.error('🔍 Transaction error details:', {
error: transaction.error,
store: store.name,
mode: transaction.mode
});
reject(transaction.error);
};
});
// Étape 2: Vérifier que le wallet est bien stocké (nouvelle transaction)
await new Promise<void>((resolve, reject) => {
const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readonly');
const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
const verificationRequest = store.get('1');
verificationRequest.onsuccess = () => {
console.log('🔍 Verification result structure:', verificationRequest.result ? {
hasPreId: !!verificationRequest.result.pre_id,
hasEncryptedDevice: !!verificationRequest.result.encrypted_device,
hasEncryptedWallet: !!verificationRequest.result.encrypted_wallet,
hasSecurityMode: !!verificationRequest.result.security_mode,
hasDeviceInClear: !!verificationRequest.result.device // DEVRAIT ÊTRE NULL
} : 'null');
if (verificationRequest.result) {
console.log('✅ Wallet verification: Found in IndexedDB with key "1"');
// Vérifier qu'il n'y a AUCUN wallet en clair
if (verificationRequest.result.device) {
console.error('❌ CRITICAL: Device in clear found in database! This should be encrypted.');
reject(new Error('Security violation: wallet stored in clear'));
return;
}
// Vérifier que les données chiffrées sont présentes
if (!verificationRequest.result.encrypted_device || !verificationRequest.result.encrypted_wallet) {
console.error('❌ Wallet verification failed: encrypted data missing');
reject(new Error('Encrypted data missing'));
return;
}
console.log('✅ Wallet stored correctly: only encrypted data present');
resolve();
} else {
console.error('❌ Wallet verification: Not found in IndexedDB with key "1"');
reject(new Error('Wallet not found after save'));
}
};
verificationRequest.onerror = () => {
console.error('❌ Wallet verification failed:', verificationRequest.error);
reject(verificationRequest.error);
};
});
// Le mode de sécurité est déjà stocké via la clé PBKDF2 dans le store pbkdf2keys
// Pas besoin de le stocker séparément
console.log('🔐 Security mode is implicitly stored via PBKDF2 key:', currentMode);
// Vérification finale : s'assurer que le wallet est bien dans IndexedDB
console.log('🔍 Final verification: checking wallet in IndexedDB...');
const finalVerification = await new Promise<any>((resolve, reject) => {
const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readonly');
const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
const request = store.get('1');
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
if (finalVerification) {
// Vérifier qu'il n'y a PAS de device en clair
if (finalVerification.device) {
console.error('❌ CRITICAL: Final verification - Device in clear found!');
throw new Error('Security violation: wallet stored in clear');
}
console.log('✅ Wallet saved exclusively in IndexedDB and verified');
console.log('🔍 Wallet contains only encrypted data:', {
hasEncryptedDevice: !!finalVerification.encrypted_device,
hasEncryptedWallet: !!finalVerification.encrypted_wallet,
securityMode: finalVerification.security_mode,
hasDeviceInClear: !!finalVerification.device // DEVRAIT ÊTRE FALSE
});
// TEST: Déchiffrer le wallet pour valider que ça fonctionne
console.log('🔐 TEST: Attempting to decrypt wallet to validate encryption...');
try {
const pbkdf2KeyTest = await secureCredentialsService.retrievePBKDF2Key(currentMode as any);
if (!pbkdf2KeyTest) {
console.error('❌ TEST: Failed to retrieve PBKDF2 key for decryption test');
} else {
console.log('✅ TEST: PBKDF2 key retrieved for decryption test');
// Déchiffrer le wallet chiffré
const decryptedWallet = await encryptionService.decrypt(
finalVerification.encrypted_wallet,
pbkdf2KeyTest
);
const parsedWallet = JSON.parse(decryptedWallet);
console.log('✅ TEST: Wallet decrypted successfully:', {
hasScanSk: !!parsedWallet.scan_sk,
hasSpendKey: !!parsedWallet.spend_key,
network: parsedWallet.network,
state: parsedWallet.state,
created_at: parsedWallet.created_at
});
// Déchiffrer le device chiffré
const decryptedDevice = await encryptionService.decrypt(
finalVerification.encrypted_device,
pbkdf2KeyTest
);
const parsedDevice = JSON.parse(decryptedDevice);
console.log('✅ TEST: Device decrypted successfully:', {
hasSpWallet: !!parsedDevice.sp_wallet,
network: parsedDevice.network
});
console.log('✅ TEST: Full decryption test passed - wallet and device decrypt correctly');
}
} catch (decryptError) {
console.error('❌ TEST: Decryption test failed:', decryptError);
console.error('❌ This indicates an issue with encryption/decryption logic');
}
} else {
console.error('❌ Final wallet verification failed - wallet not found in IndexedDB');
throw new Error('Wallet verification failed - wallet not found');
}
} catch (error) {
console.error('❌ Error during wallet save:', error);
updateStatus('❌ Erreur: Échec de la sauvegarde du wallet', 'error');
throw error;
}
// Étape 4: Finalisation
updateStatus('✅ Wallet sauvegardé avec succès!', 'success');
updateProgress(100);
console.log('🎉 Wallet setup completed successfully - wallet saved with birthday_waiting state');
console.log('🔗 Ready to proceed to network connection and birthday setup');
// Activer le bouton continuer
continueBtn.disabled = false;
} catch (error) {
console.error('❌ Error during wallet setup:', error);
updateStatus('❌ Erreur lors de la génération du wallet', 'error');
}
// Gestion du bouton continuer
continueBtn.addEventListener('click', () => {
console.log('🔗 Redirecting to pairing page...');
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
});
});