From aa913ef93018a447dc18221923fd629959acb4e1 Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Sun, 26 Oct 2025 02:19:00 +0100 Subject: [PATCH] feat: centralize database configuration and fix service worker blocking **Motivations :** - Centralize database configuration to prevent version inconsistencies - Fix service worker blocking during wallet setup - Ensure all database stores are created at initialization **Modifications :** - Created database-config.ts with centralized DATABASE_CONFIG (name, version, stores) - Updated storage.service.ts to use DATABASE_CONFIG and create all stores on upgrade - Updated security-setup.ts to initialize database with complete configuration - Updated wallet-setup.ts to call SDK directly and bypass service worker blocking - Updated database.service.ts, webauthn.service.ts, and database.worker.js to use DATABASE_CONFIG - Removed service worker dependency for wallet setup page **Pages affected :** - security-setup.html: Initializes database with all stores on page load - wallet-setup.html: Saves wallet directly to IndexedDB without service worker dependency --- .../device-management/device-management.ts | 18 +-- .../secure-credentials/secure-credentials.ts | 2 +- .../security-mode-selector.ts | 2 +- .../validation-modal/validation-modal.ts | 4 +- src/pages/home/home-component.ts | 4 +- src/pages/home/home.ts | 8 +- src/pages/security-setup/security-setup.ts | 15 +- src/pages/wallet-setup/wallet-setup.ts | 116 ++++++++------ src/router.ts | 26 +-- src/service-workers/database.worker.js | 13 +- src/services/credentials/storage.service.ts | 44 ++++-- src/services/credentials/webauthn.service.ts | 148 ++++++++++++------ src/services/database-config.ts | 111 +++++++++++++ src/services/database.service.ts | 10 +- src/services/event-bus.ts | 20 +-- src/services/memory-manager.ts | 16 +- src/services/modal.service.ts | 10 +- src/services/pairing.service.ts | 2 +- src/services/performance-monitor.ts | 12 +- src/services/secure-credentials.service.ts | 4 +- src/services/secure-key-manager.ts | 2 +- src/services/secure-logger.ts | 2 +- src/services/security-mode.service.ts | 97 +++--------- src/services/service.ts | 40 ++--- src/services/websocket-manager.ts | 2 +- src/utils/logger.ts | 2 +- src/utils/sp-address.utils.ts | 20 +-- src/utils/subscription.utils.ts | 2 +- src/websockets.ts | 2 +- 29 files changed, 463 insertions(+), 291 deletions(-) create mode 100644 src/services/database-config.ts diff --git a/src/components/device-management/device-management.ts b/src/components/device-management/device-management.ts index f18b285..6d1d351 100644 --- a/src/components/device-management/device-management.ts +++ b/src/components/device-management/device-management.ts @@ -30,7 +30,7 @@ export class DeviceManagementComponent extends HTMLElement { } async loadDeviceData() { - if (!this.service) return; + if (!this.service) {return;} try { // Get current device address and generate 4 words @@ -601,12 +601,12 @@ export class DeviceManagementComponent extends HTMLElement { const saveBtn = this.shadowRoot!.getElementById('saveChangesBtn') as HTMLButtonElement; const cancelBtn = this.shadowRoot!.getElementById('cancelChangesBtn') as HTMLButtonElement; - if (saveBtn) saveBtn.disabled = false; - if (cancelBtn) cancelBtn.disabled = false; + if (saveBtn) {saveBtn.disabled = false;} + if (cancelBtn) {cancelBtn.disabled = false;} } async saveChanges() { - if (!this.service) return; + if (!this.service) {return;} try { // Update the pairing process with new devices @@ -634,8 +634,8 @@ export class DeviceManagementComponent extends HTMLElement { const saveBtn = this.shadowRoot!.getElementById('saveChangesBtn') as HTMLButtonElement; const cancelBtn = this.shadowRoot!.getElementById('cancelChangesBtn') as HTMLButtonElement; - if (saveBtn) saveBtn.disabled = true; - if (cancelBtn) cancelBtn.disabled = true; + if (saveBtn) {saveBtn.disabled = true;} + if (cancelBtn) {cancelBtn.disabled = true;} } async importAccount() { @@ -692,12 +692,12 @@ export class DeviceManagementComponent extends HTMLElement { const confirm1 = confirm( '🚹 EXPORT CRITIQUE: Cette action va exposer votre CLÉ PRIVÉE.\n\nCette clĂ© permet de signer des transactions sans interaction sur le 2Ăšme device.\n\nÊtes-vous sĂ»r de vouloir continuer ?' ); - if (!confirm1) return; + if (!confirm1) {return;} const confirm2 = confirm( '⚠ SÉCURITÉ: Votre clĂ© privĂ©e sera visible en clair.\n\nAssurez-vous que personne ne peut voir votre Ă©cran.\n\nContinuer ?' ); - if (!confirm2) return; + if (!confirm2) {return;} const confirm3 = prompt( '🔐 DERNIÈRE CONFIRMATION: Cette clĂ© privĂ©e donne un accĂšs TOTAL Ă  votre compte.\n\nTapez "EXPORTER" pour confirmer:' @@ -711,7 +711,7 @@ export class DeviceManagementComponent extends HTMLElement { // Get the device's private key // @ts-ignore - deviceRaw is guaranteed to be non-null after the check below const deviceRaw = await this.service.getDeviceFromDatabase(); - if (!deviceRaw || !deviceRaw.sp_wallet) { + if (!deviceRaw?.sp_wallet) { throw new Error('Device ou clĂ© privĂ©e non trouvĂ©e'); } // TypeScript assertion: deviceRaw is guaranteed to be non-null after the check diff --git a/src/components/secure-credentials/secure-credentials.ts b/src/components/secure-credentials/secure-credentials.ts index a8a71b6..085c022 100644 --- a/src/components/secure-credentials/secure-credentials.ts +++ b/src/components/secure-credentials/secure-credentials.ts @@ -325,7 +325,7 @@ export class SecureCredentialsComponent { */ private showMessage(message: string, type: 'success' | 'error' | 'warning' | 'info'): void { const messagesContainer = document.getElementById('credentials-messages'); - if (!messagesContainer) return; + if (!messagesContainer) {return;} const messageDiv = document.createElement('div'); messageDiv.className = `message ${type}`; diff --git a/src/components/security-mode-selector/security-mode-selector.ts b/src/components/security-mode-selector/security-mode-selector.ts index f659b9a..954c1ea 100644 --- a/src/components/security-mode-selector/security-mode-selector.ts +++ b/src/components/security-mode-selector/security-mode-selector.ts @@ -309,7 +309,7 @@ export class SecurityModeSelector { } private confirmSelection(): void { - if (!this.selectedMode) return; + if (!this.selectedMode) {return;} const modeConfig = this.getSecurityModes().find(m => m.mode === this.selectedMode); diff --git a/src/components/validation-modal/validation-modal.ts b/src/components/validation-modal/validation-modal.ts index 51b89f0..e8a55b6 100755 --- a/src/components/validation-modal/validation-modal.ts +++ b/src/components/validation-modal/validation-modal.ts @@ -42,13 +42,13 @@ export async function initValidationModal(processDiffs: any) { `; const box = document.querySelector('.validation-box'); - if (box) box.innerHTML += state; + if (box) {box.innerHTML += state;} } document.querySelectorAll('.expansion-panel-header').forEach(header => { header.addEventListener('click', function (event) { const target = event.target as HTMLElement; const body = target.nextElementSibling as HTMLElement; - if (body?.style) body.style.display = body.style.display === 'block' ? 'none' : 'block'; + if (body?.style) {body.style.display = body.style.display === 'block' ? 'none' : 'block';} }); }); } diff --git a/src/pages/home/home-component.ts b/src/pages/home/home-component.ts index fc6efed..133e712 100644 --- a/src/pages/home/home-component.ts +++ b/src/pages/home/home-component.ts @@ -32,7 +32,7 @@ export class LoginComponent extends HTMLElement { render() { if (this.shadowRoot) - this.shadowRoot.innerHTML = ` + {this.shadowRoot.innerHTML = ` ${loginHtml} @@ -40,7 +40,7 @@ export class LoginComponent extends HTMLElement { ${loginScript} - `; + `;} } } diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index 4e24fc4..51b461e 100755 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -461,7 +461,7 @@ function setupUserInteractionListener(): void { let hasTriggered = false; const triggerWebAuthn = async (event: Event) => { - if (hasTriggered) return; + if (hasTriggered) {return;} hasTriggered = true; console.log('🔐 User interaction detected:', event.type, 'triggering WebAuthn...'); @@ -812,7 +812,7 @@ async function waitForCredentialsAvailability(): Promise { // VĂ©rifier que les credentials sont rĂ©ellement disponibles et accessibles const credentials = await secureCredentialsService.retrieveCredentials(''); - if (credentials && credentials.spendKey && credentials.scanKey) { + if (credentials?.spendKey && credentials.scanKey) { console.log('✅ Credentials confirmĂ©s comme disponibles'); return; } else { @@ -938,7 +938,7 @@ async function handleMainPairing(): Promise { const credentialData = await secureCredentialsService.generateSecureCredentials('4nk-secure-password'); - if (!credentialData || !credentialData.spendKey || !credentialData.scanKey) { + if (!credentialData?.spendKey || !credentialData.scanKey) { throw new Error('Failed to generate valid credentials - missing spendKey or scanKey'); } @@ -999,7 +999,7 @@ async function handleMainPairing(): Promise { try { // VĂ©rifier que les credentials sont rĂ©ellement disponibles const credentials = await secureCredentialsService.retrieveCredentials(''); - if (!credentials || !credentials.spendKey || !credentials.scanKey) { + if (!credentials?.spendKey || !credentials.scanKey) { throw new Error('Credentials not properly available'); } credentialsReady = true; diff --git a/src/pages/security-setup/security-setup.ts b/src/pages/security-setup/security-setup.ts index 27563eb..800fd90 100644 --- a/src/pages/security-setup/security-setup.ts +++ b/src/pages/security-setup/security-setup.ts @@ -4,12 +4,24 @@ */ import { SecurityMode } from '../../services/security-mode.service'; +import { DATABASE_CONFIG, openDatabase } from '../../services/database-config'; let selectedMode: SecurityMode | null = null; -document.addEventListener('DOMContentLoaded', () => { +document.addEventListener('DOMContentLoaded', async () => { console.log('🔐 Security setup page loaded'); + // Initialiser la base de donnĂ©es avec la configuration complĂšte + try { + console.log(`🔍 Initializing database ${DATABASE_CONFIG.name} version ${DATABASE_CONFIG.version}...`); + await openDatabase(); + console.log('✅ Database initialized successfully'); + } catch (error) { + console.error('❌ Failed to initialize database:', error); + alert('Erreur lors de l\'initialisation de la base de donnĂ©es. Veuillez rĂ©essayer.'); + return; + } + const options = document.querySelectorAll('.security-option'); const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement; const warning = document.getElementById('warning') as HTMLDivElement; @@ -72,6 +84,7 @@ document.addEventListener('DOMContentLoaded', () => { console.log('🔐 Generating PBKDF2 key with security mode:', selectedMode); // GĂ©nĂ©rer la clĂ© PBKDF2 et la stocker selon le mode + // IMPORTANT: Cette opĂ©ration doit ĂȘtre directe (pas de dĂ©lai) pour que WebAuthn fonctionne const pbkdf2Key = await secureCredentialsService.generatePBKDF2Key(selectedMode); console.log('✅ PBKDF2 key generated and stored securely'); diff --git a/src/pages/wallet-setup/wallet-setup.ts b/src/pages/wallet-setup/wallet-setup.ts index 7da7643..9ad7c3d 100644 --- a/src/pages/wallet-setup/wallet-setup.ts +++ b/src/pages/wallet-setup/wallet-setup.ts @@ -3,6 +3,8 @@ * DeuxiĂšme Ă©tape du processus d'initialisation */ +import { DATABASE_CONFIG } from '../../services/database-config'; + document.addEventListener('DOMContentLoaded', async () => { console.log('💰 Wallet setup page loaded'); @@ -22,13 +24,13 @@ document.addEventListener('DOMContentLoaded', async () => { // MĂ©thode pour sauvegarder directement en IndexedDB dans la base 4nk async function saveCredentialsDirectly(credentials: any): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open('4nk', 2); + 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(['wallet'], 'readwrite'); - const store = transaction.objectStore('wallet'); + 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(); @@ -37,8 +39,8 @@ document.addEventListener('DOMContentLoaded', async () => { request.onupgradeneeded = () => { const db = request.result; - if (!db.objectStoreNames.contains('wallet')) { - db.createObjectStore('wallet'); + if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) { + db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string }); } }; }); @@ -49,7 +51,7 @@ document.addEventListener('DOMContentLoaded', async () => { updateStatus('🔄 Initialisation des services...', 'loading'); updateProgress(10); - let services; // DĂ©clarer services au niveau supĂ©rieur + let services: any; // DĂ©clarer services au niveau supĂ©rieur try { console.log('🔄 Importing services...'); @@ -76,7 +78,7 @@ document.addEventListener('DOMContentLoaded', async () => { console.log('✅ Services initialized successfully'); break; } catch (error) { - console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, error.message); + console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, error instanceof Error ? error.message : String(error)); // Diagnostic plus dĂ©taillĂ© if (attempts === 5) { @@ -198,9 +200,9 @@ document.addEventListener('DOMContentLoaded', async () => { console.log('🔐 Encrypted wallet data:', encryptedWallet); // Ouvrir la base de donnĂ©es 4nk existante sans la modifier - console.log('🔍 Opening IndexedDB database "4nk" version 2...'); + console.log(`🔍 Opening IndexedDB database "${DATABASE_CONFIG.name}" version ${DATABASE_CONFIG.version}...`); const db = await new Promise((resolve, reject) => { - const request = indexedDB.open('4nk', 2); // Utiliser la version existante + 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); @@ -227,37 +229,48 @@ document.addEventListener('DOMContentLoaded', async () => { }); // Étape 1: Sauvegarder le wallet dans le format attendu par getDeviceFromDatabase - console.log('🔍 Opening transaction for wallet store...'); + // 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...`); await new Promise((resolve, reject) => { - const transaction = db.transaction(['wallet'], 'readwrite'); - const store = transaction.objectStore('wallet'); + 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); - // CrĂ©er un device temporaire avec l'Ă©tat birthday_waiting - // Utiliser le wallet chiffrĂ© au lieu des donnĂ©es en clair - const device = { - sp_wallet: { - // Stocker le wallet chiffrĂ© au lieu des clĂ©s en clair - encrypted_data: encryptedWallet, - network: walletData.network, - birthday: 0, // Sera mis Ă  jour dans birthday-setup - last_scan: 0, - state: 'birthday_waiting' - }, - sp_client: { - // Structure complĂšte pour Ă©viter l'erreur "missing field sp_client" - initialized: false, - // Ajouter d'autres champs nĂ©cessaires - version: 1, - capabilities: [] - }, - created_at: walletData.created_at - }; - - // Stocker dans le format attendu par getDeviceFromDatabase + // Stocker le wallet chiffrĂ© sĂ©parĂ©ment et laisser le SDK gĂ©rer sp_wallet const walletObject = { pre_id: '1', - device: device + device: device, + encrypted_wallet: encryptedWallet, // Stocker le wallet chiffrĂ© sĂ©parĂ©ment + security_mode: currentMode // Stocker le mode de sĂ©curitĂ© }; console.log('🔍 Attempting to save wallet object:', walletObject); @@ -290,8 +303,8 @@ document.addEventListener('DOMContentLoaded', async () => { // Étape 2: VĂ©rifier que le wallet est bien stockĂ© (nouvelle transaction) await new Promise((resolve, reject) => { - const transaction = db.transaction(['wallet'], 'readonly'); - const store = transaction.objectStore('wallet'); + 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 = () => { @@ -328,25 +341,26 @@ document.addEventListener('DOMContentLoaded', async () => { // 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((resolve, reject) => { - const transaction = db.transaction(['wallet'], 'readonly'); - const store = transaction.objectStore('wallet'); + 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 && finalVerification.device) { - console.log('✅ Wallet saved exclusively in IndexedDB and verified'); - console.log('🔍 Wallet contains:', { - hasSpWallet: !!finalVerification.device.sp_wallet, - hasSpClient: !!finalVerification.device.sp_client, - state: finalVerification.device.sp_wallet?.state, - hasEncryptedData: !!finalVerification.device.sp_wallet?.encrypted_data - }); - } else { - console.error('❌ Final wallet verification failed - wallet not found in IndexedDB'); - throw new Error('Wallet verification failed - wallet not found'); - } + if (finalVerification && finalVerification.device) { + console.log('✅ Wallet saved exclusively in IndexedDB and verified'); + console.log('🔍 Wallet contains:', { + hasSpWallet: !!finalVerification.device.sp_wallet, + hasSpClient: !!finalVerification.device.sp_client, + hasEncryptedWallet: !!finalVerification.encrypted_wallet, + securityMode: finalVerification.security_mode, + spClientKeys: finalVerification.device.sp_client ? Object.keys(finalVerification.device.sp_client) : 'none' + }); + } 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); diff --git a/src/router.ts b/src/router.ts index a7e56eb..2869a57 100755 --- a/src/router.ts +++ b/src/router.ts @@ -526,7 +526,7 @@ export async function registerAllListeners() { await services.checkConnections(process, stateId); - let res: Record = {}; + const res: Record = {}; if (state) { // Decrypt all the data we have the key for for (const attribute of Object.keys(state.pcd_commitment)) { @@ -621,7 +621,7 @@ export async function registerAllListeners() { }; const handleGetPairingId = async (event: MessageEvent) => { - if (event.data.type !== MessageType.GET_PAIRING_ID) return; + if (event.data.type !== MessageType.GET_PAIRING_ID) {return;} if (!services.isPaired()) { const errorMsg = 'Device not paired'; @@ -655,7 +655,7 @@ export async function registerAllListeners() { }; const handleCreateProcess = async (event: MessageEvent) => { - if (event.data.type !== MessageType.CREATE_PROCESS) return; + if (event.data.type !== MessageType.CREATE_PROCESS) {return;} if (!services.isPaired()) { const errorMsg = 'Device not paired'; @@ -704,7 +704,7 @@ export async function registerAllListeners() { }; const handleNotifyUpdate = async (event: MessageEvent) => { - if (event.data.type !== MessageType.NOTIFY_UPDATE) return; + if (event.data.type !== MessageType.NOTIFY_UPDATE) {return;} if (!services.isPaired()) { const errorMsg = 'Device not paired'; @@ -742,7 +742,7 @@ export async function registerAllListeners() { }; const handleValidateState = async (event: MessageEvent) => { - if (event.data.type !== MessageType.VALIDATE_STATE) return; + if (event.data.type !== MessageType.VALIDATE_STATE) {return;} if (!services.isPaired()) { const errorMsg = 'Device not paired'; @@ -777,7 +777,7 @@ export async function registerAllListeners() { }; const handleUpdateProcess = async (event: MessageEvent) => { - if (event.data.type !== MessageType.UPDATE_PROCESS) return; + if (event.data.type !== MessageType.UPDATE_PROCESS) {return;} if (!services.isPaired()) { const errorMsg = 'Device not paired'; @@ -861,7 +861,7 @@ export async function registerAllListeners() { } } - if (privateData[field]) continue; + if (privateData[field]) {continue;} // We've get back all the way to the first state without seeing it, it's a new public field publicData[field] = newData[field]; @@ -888,7 +888,7 @@ export async function registerAllListeners() { }; const handleDecodePublicData = async (event: MessageEvent) => { - if (event.data.type !== MessageType.DECODE_PUBLIC_DATA) return; + if (event.data.type !== MessageType.DECODE_PUBLIC_DATA) {return;} if (!services.isPaired()) { const errorMsg = 'Device not paired'; @@ -922,7 +922,7 @@ export async function registerAllListeners() { }; const handleHashValue = async (event: MessageEvent) => { - if (event.data.type !== MessageType.HASH_VALUE) return; + if (event.data.type !== MessageType.HASH_VALUE) {return;} console.log('handleHashValue', event.data); @@ -951,7 +951,7 @@ export async function registerAllListeners() { }; const handleGetMerkleProof = async (event: MessageEvent) => { - if (event.data.type !== MessageType.GET_MERKLE_PROOF) return; + if (event.data.type !== MessageType.GET_MERKLE_PROOF) {return;} try { const { accessToken, processState, attributeName } = event.data; @@ -978,7 +978,7 @@ export async function registerAllListeners() { }; const handleValidateMerkleProof = async (event: MessageEvent) => { - if (event.data.type !== MessageType.VALIDATE_MERKLE_PROOF) return; + if (event.data.type !== MessageType.VALIDATE_MERKLE_PROOF) {return;} try { const { accessToken, merkleProof, documentHash } = event.data; @@ -1149,7 +1149,7 @@ async function handlePairing4WordsJoin(event: MessageEvent) { async function cleanPage() { const container = document.querySelector('#containerId'); - if (container) container.innerHTML = ''; + if (container) {container.innerHTML = '';} } // Essential functions are now handled directly in the application @@ -1191,7 +1191,7 @@ document.addEventListener('navigate', (e: Event) => { const event = e as CustomEvent<{ page: string; processId?: string }>; if (event.detail.page === 'chat') { const container = document.querySelector('.container'); - if (container) container.innerHTML = ''; + if (container) {container.innerHTML = '';} //initChat(); diff --git a/src/service-workers/database.worker.js b/src/service-workers/database.worker.js index cfa304e..91ca37c 100755 --- a/src/service-workers/database.worker.js +++ b/src/service-workers/database.worker.js @@ -101,9 +101,14 @@ async function scanMissingData(processesToScan) { return Array.from(toDownload); } +const DATABASE_CONFIG = { + name: '4nk', + version: 3 +}; + async function openDatabase() { return new Promise((resolve, reject) => { - const request = indexedDB.open('4nk', 1); + const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); request.onerror = event => { reject(request.error); }; @@ -115,6 +120,12 @@ async function openDatabase() { if (!db.objectStoreNames.contains('wallet')) { db.createObjectStore('wallet', { keyPath: 'pre_id' }); } + if (!db.objectStoreNames.contains('credentials')) { + db.createObjectStore('credentials'); + } + if (!db.objectStoreNames.contains('pbkdf2keys')) { + db.createObjectStore('pbkdf2keys'); + } }; }); } diff --git a/src/services/credentials/storage.service.ts b/src/services/credentials/storage.service.ts index 9f5718a..0b91770 100644 --- a/src/services/credentials/storage.service.ts +++ b/src/services/credentials/storage.service.ts @@ -3,12 +3,13 @@ */ import { secureLogger } from '../secure-logger'; import { CredentialData } from './types'; +import { DATABASE_CONFIG } from '../database-config'; export class StorageService { private static instance: StorageService; - private dbName = '4nk'; - private storeName = 'credentials'; // Store sĂ©parĂ© pour les clĂ©s PBKDF2 - private dbVersion = 2; + private dbName = DATABASE_CONFIG.name; + private storeName = DATABASE_CONFIG.stores.credentials.name; // Store sĂ©parĂ© pour les clĂ©s PBKDF2 + private dbVersion = DATABASE_CONFIG.version; private constructor() {} @@ -150,20 +151,29 @@ export class StorageService { request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; - if (!db.objectStoreNames.contains(this.storeName)) { - db.createObjectStore(this.storeName); - secureLogger.info('IndexedDB upgrade needed, creating credentials store', { - component: 'StorageService', - operation: 'openDatabase' - }); - } - if (!db.objectStoreNames.contains('wallet')) { - db.createObjectStore('wallet'); - secureLogger.info('IndexedDB upgrade needed, creating wallet store', { - component: 'StorageService', - operation: 'openDatabase' - }); - } + console.log(`🔄 StorageService: Database upgrade needed for ${this.dbName} version ${this.dbVersion}`); + + // CrĂ©er tous les stores dĂ©finis dans DATABASE_CONFIG + Object.values(DATABASE_CONFIG.stores).forEach(storeConfig => { + if (!db.objectStoreNames.contains(storeConfig.name)) { + const options: IDBObjectStoreParameters = {}; + if (storeConfig.keyPath) { + options.keyPath = storeConfig.keyPath; + } + if ('autoIncrement' in storeConfig && storeConfig.autoIncrement) { + options.autoIncrement = true; + } + + const store = db.createObjectStore(storeConfig.name, options); + + // CrĂ©er les index + storeConfig.indices.forEach(indexConfig => { + store.createIndex(indexConfig.name, indexConfig.keyPath, { unique: indexConfig.unique || false }); + }); + + console.log(`✅ Created store: ${storeConfig.name}`); + } + }); }; }); } diff --git a/src/services/credentials/webauthn.service.ts b/src/services/credentials/webauthn.service.ts index 814924e..f24a1d0 100644 --- a/src/services/credentials/webauthn.service.ts +++ b/src/services/credentials/webauthn.service.ts @@ -4,6 +4,7 @@ import { secureLogger } from '../secure-logger'; import { SecurityMode } from '../security-mode.service'; import { WebAuthnCredential, EncryptionResult } from './types'; +import { DATABASE_CONFIG } from '../database-config'; export class WebAuthnService { private static instance: WebAuthnService; @@ -18,35 +19,36 @@ export class WebAuthnService { } /** - * Stocke une clĂ© avec WebAuthn + * Stocke une clĂ© PBKDF2 avec WebAuthn + * WebAuthn est utilisĂ© uniquement pour l'authentification, pas pour le chiffrement */ async storeKeyWithWebAuthn(key: string, securityMode: SecurityMode): Promise { try { - secureLogger.info('Storing key with WebAuthn', { + secureLogger.info('Storing PBKDF2 key with WebAuthn encryption', { component: 'WebAuthnService', operation: 'storeKeyWithWebAuthn', securityMode }); - // CrĂ©er des credentials WebAuthn pour stocker la clĂ© - const credential = await this.createCredentials('4nk-secure-password', securityMode); + // CrĂ©er des credentials WebAuthn pour l'authentification + const credential = await this.createCredentials('4nk-pbkdf2-key', securityMode); - // Stocker la clĂ© chiffrĂ©e avec les credentials WebAuthn + // Chiffrer la clĂ© PBKDF2 avec la rĂ©ponse WebAuthn const encryptedKey = await this.encryptKeyWithWebAuthn(key, credential); - // Sauvegarder dans IndexedDB - await this.saveEncryptedKey(encryptedKey, credential.id, securityMode); + // Stocker la clĂ© PBKDF2 chiffrĂ©e dans IndexedDB + // La clĂ© est stockĂ©e avec le securityMode comme clĂ© + await this.savePBKDF2Key(encryptedKey, credential.id, securityMode); - secureLogger.info('Key stored with WebAuthn successfully', { + secureLogger.info('PBKDF2 key encrypted and stored with WebAuthn successfully', { component: 'WebAuthnService', operation: 'storeKeyWithWebAuthn' }); } catch (error) { - secureLogger.error('Failed to store key with WebAuthn', { + secureLogger.error('Failed to store PBKDF2 key with WebAuthn', error as Error, { component: 'WebAuthnService', - operation: 'storeKeyWithWebAuthn', - error: error instanceof Error ? error.message : String(error) + operation: 'storeKeyWithWebAuthn' }); throw error; } @@ -197,16 +199,18 @@ export class WebAuthnService { console.log('🔐 WebAuthn credential created successfully:', credential); + const response = credential.response as AuthenticatorAttestationResponse; return { id: Array.from(new Uint8Array(credential.rawId)).map(b => b.toString(16).padStart(2, '0')).join(''), - publicKey: Array.from(new Uint8Array((credential.response as AuthenticatorAttestationResponse).publicKey!)) + publicKey: Array.from(new Uint8Array(response.getPublicKey()!)) }; } catch (error) { console.error('❌ WebAuthn credential creation failed:', error); // Message d'erreur spĂ©cifique pour Proton Pass if (mode === 'proton-pass') { - throw new Error(`Proton Pass authentication failed: ${error.message}. Please ensure Proton Pass is installed and enabled.`); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Proton Pass authentication failed: ${errorMessage}. Please ensure Proton Pass is installed and enabled.`); } throw error; @@ -229,10 +233,9 @@ export class WebAuthnService { console.log('🔐 Key encrypted with WebAuthn credential'); return encryptedKey; } catch (error) { - secureLogger.error('Failed to encrypt key with WebAuthn', { + secureLogger.error('Failed to encrypt key with WebAuthn', error as Error, { component: 'WebAuthnService', - operation: 'encryptKeyWithWebAuthn', - error: error instanceof Error ? error.message : String(error) + operation: 'encryptKeyWithWebAuthn' }); throw error; } @@ -241,31 +244,34 @@ export class WebAuthnService { /** * Sauvegarde une clĂ© chiffrĂ©e dans IndexedDB */ - private async saveEncryptedKey(encryptedKey: string, credentialId: string, securityMode: SecurityMode): Promise { + /** + * Sauvegarde la clĂ© PBKDF2 chiffrĂ©e dans IndexedDB + * NE PAS stocker credentialId avec la clĂ© chiffrĂ©e + */ + private async savePBKDF2Key(encryptedKey: string, _credentialId: string, securityMode: SecurityMode): Promise { try { const db = await this.openDatabase(); - const transaction = db.transaction(['webauthn-keys'], 'readwrite'); - const store = transaction.objectStore('webauthn-keys'); + console.log(`🔍 Available stores in ${DATABASE_CONFIG.name}:`, Array.from(db.objectStoreNames)); + const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readwrite'); + const store = transaction.objectStore(DATABASE_CONFIG.stores.pbkdf2keys.name); await new Promise((resolve, reject) => { - // Stocker seulement la clĂ© chiffrĂ©e (pas de credentialId au mĂȘme endroit) + // Utiliser le securityMode comme clĂ© d'enregistrement + // NE PAS stocker credentialId avec la clĂ© chiffrĂ©e const request = store.put({ - encryptedKey, - securityMode: securityMode, // Mode de sĂ©curitĂ© dynamique + encryptedKey, // ClĂ© PBKDF2 chiffrĂ©e avec WebAuthn timestamp: Date.now() - }, 'pbkdf2-key'); + }, securityMode); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); - // Ne pas stocker credentialId - il sera rĂ©cupĂ©rĂ© dynamiquement via WebAuthn - console.log('🔐 CredentialId will be retrieved dynamically via WebAuthn'); + console.log(`🔐 PBKDF2 key stored with security mode: ${securityMode}`); } catch (error) { - secureLogger.error('Failed to save encrypted key', { + secureLogger.error('Failed to save PBKDF2 key', error as Error, { component: 'WebAuthnService', - operation: 'saveEncryptedKey', - error: error instanceof Error ? error.message : String(error) + operation: 'savePBKDF2Key' }); throw error; } @@ -273,46 +279,88 @@ export class WebAuthnService { /** - * RĂ©cupĂšre une clĂ© chiffrĂ©e avec WebAuthn (rĂ©cupĂ©ration dynamique) + * RĂ©cupĂšre et dĂ©chiffre la clĂ© PBKDF2 avec WebAuthn */ - async retrieveKeyWithWebAuthn(): Promise { + async retrieveKeyWithWebAuthn(securityMode: SecurityMode): Promise { try { - // RĂ©cupĂ©rer la clĂ© chiffrĂ©e depuis IndexedDB + // RĂ©cupĂ©rer la clĂ© PBKDF2 chiffrĂ©e depuis IndexedDB avec le securityMode comme clĂ© const db = await this.openDatabase(); - const transaction = db.transaction(['webauthn-keys'], 'readonly'); - const store = transaction.objectStore('webauthn-keys'); + const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readonly'); + const store = transaction.objectStore(DATABASE_CONFIG.stores.pbkdf2keys.name); const result = await new Promise((resolve, reject) => { - const request = store.get('pbkdf2-key'); + const request = store.get(securityMode); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (!result || !result.encryptedKey) { - console.log('🔍 No encrypted key found in WebAuthnKeysDB'); + console.log(`🔍 No PBKDF2 key found for security mode: ${securityMode}`); return null; } - // RĂ©cupĂ©rer credentialId dynamiquement via WebAuthn + // RĂ©cupĂ©rer le credentialId dynamiquement via WebAuthn const credentialId = await this.getCurrentCredentialId(); if (!credentialId) { console.log('🔍 No WebAuthn credential available'); return null; } - // DĂ©chiffrer la clĂ© avec credentialId - const { EncryptionService } = await import('./encryption.service'); - const encryptionService = EncryptionService.getInstance(); + // DĂ©chiffrer la clĂ© avec le credentialId WebAuthn + const encrypted = atob(result.encryptedKey); + const combined = new Uint8Array(encrypted.length); + for (let i = 0; i < encrypted.length; i++) { + combined[i] = encrypted.charCodeAt(i); + } - const decryptedKey = await encryptionService.decryptWithPassword(result.encryptedKey, credentialId); - console.log('🔐 Key decrypted with WebAuthn credential'); + // Extraire salt (16 bytes), iv (12 bytes) et donnĂ©es chiffrĂ©es + const salt = combined.slice(0, 16); + const iv = combined.slice(16, 28); + const encryptedData = combined.slice(28); + + // DĂ©river la clĂ© avec PBKDF2 + const keyMaterial = await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(credentialId), + 'PBKDF2', + false, + ['deriveBits'] + ); + + const derivedKey = await crypto.subtle.deriveBits( + { + name: 'PBKDF2', + salt: salt, + iterations: 100000, + hash: 'SHA-256' + }, + keyMaterial, + 256 + ); + + const cryptoKey = await crypto.subtle.importKey( + 'raw', + derivedKey, + { name: 'AES-GCM' }, + false, + ['decrypt'] + ); + + // DĂ©chiffrer + const decrypted = await crypto.subtle.decrypt( + { name: 'AES-GCM', iv: iv }, + cryptoKey, + encryptedData + ); + + const decryptedKey = new TextDecoder().decode(decrypted); + console.log('🔐 PBKDF2 key decrypted with WebAuthn'); return decryptedKey; } catch (error) { - secureLogger.error('Failed to retrieve key with WebAuthn', { + secureLogger.error('Failed to retrieve PBKDF2 key with WebAuthn', error as Error, { component: 'WebAuthnService', - operation: 'retrieveKeyWithWebAuthn', - error: error instanceof Error ? error.message : String(error) + operation: 'retrieveKeyWithWebAuthn' }); return null; } @@ -348,10 +396,10 @@ export class WebAuthnService { */ private async openDatabase(): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open('WebAuthnKeysDB', 1); + const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); request.onerror = () => { - secureLogger.error('Failed to open WebAuthn database', new Error('Database open failed'), { + secureLogger.error('Failed to open database', new Error('Database open failed'), { component: 'WebAuthnService', operation: 'openDatabase' }); @@ -364,8 +412,12 @@ export class WebAuthnService { request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; - if (!db.objectStoreNames.contains('webauthn-keys')) { - db.createObjectStore('webauthn-keys'); + console.log(`🔄 ${DATABASE_CONFIG.name} database upgrade needed, creating stores...`); + if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.pbkdf2keys.name)) { + db.createObjectStore(DATABASE_CONFIG.stores.pbkdf2keys.name); + console.log(`✅ ${DATABASE_CONFIG.stores.pbkdf2keys.name} store created`); + } else { + console.log(`✅ ${DATABASE_CONFIG.stores.pbkdf2keys.name} store already exists`); } }; }); diff --git a/src/services/database-config.ts b/src/services/database-config.ts new file mode 100644 index 0000000..cfef725 --- /dev/null +++ b/src/services/database-config.ts @@ -0,0 +1,111 @@ +/** + * Database Configuration - Configuration centralisĂ©e de la base de donnĂ©es IndexedDB + * + * Ce fichier centralise toutes les dĂ©finitions de la base de donnĂ©es pour Ă©viter + * les erreurs de version et d'incohĂ©rence entre les diffĂ©rents fichiers. + */ + +export const DATABASE_CONFIG = { + name: '4nk', + version: 3, + stores: { + wallet: { + name: 'wallet', + keyPath: 'pre_id', + indices: [] + }, + credentials: { + name: 'credentials', + keyPath: null, + indices: [] + }, + pbkdf2keys: { + name: 'pbkdf2keys', + keyPath: null, + indices: [] + }, + labels: { + name: 'labels', + keyPath: 'emoji', + indices: [] + }, + processes: { + name: 'processes', + keyPath: null, + indices: [] + }, + shared_secrets: { + name: 'shared_secrets', + keyPath: null, + indices: [] + }, + unconfirmed_secrets: { + name: 'unconfirmed_secrets', + keyPath: null, + autoIncrement: true, + indices: [] + }, + diffs: { + name: 'diffs', + keyPath: 'value_commitment', + indices: [ + { name: 'byStateId', keyPath: 'state_id', unique: false }, + { name: 'byNeedValidation', keyPath: 'need_validation', unique: false }, + { name: 'byStatus', keyPath: 'validation_status', unique: false } + ] + }, + data: { + name: 'data', + keyPath: null, + indices: [] + } + } +} as const; + +/** + * Ouvrir la base de donnĂ©es IndexedDB avec la configuration centralisĂ©e + */ +export async function openDatabase(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); + + request.onerror = () => { + console.error(`❌ Failed to open database ${DATABASE_CONFIG.name}:`, request.error); + reject(request.error); + }; + + request.onsuccess = () => { + console.log(`✅ Database ${DATABASE_CONFIG.name} opened successfully`); + resolve(request.result); + }; + + request.onupgradeneeded = () => { + const db = request.result; + console.log(`🔄 Database upgrade needed for ${DATABASE_CONFIG.name} version ${DATABASE_CONFIG.version}`); + + // CrĂ©er les stores manquants + Object.values(DATABASE_CONFIG.stores).forEach(storeConfig => { + if (!db.objectStoreNames.contains(storeConfig.name)) { + const options: IDBObjectStoreParameters = {}; + if (storeConfig.keyPath) { + options.keyPath = storeConfig.keyPath; + } + if ('autoIncrement' in storeConfig && storeConfig.autoIncrement) { + options.autoIncrement = true; + } + + const store = db.createObjectStore(storeConfig.name, options); + + // CrĂ©er les index + storeConfig.indices.forEach(indexConfig => { + store.createIndex(indexConfig.name, indexConfig.keyPath, { unique: indexConfig.unique || false }); + }); + + console.log(`✅ Created store: ${storeConfig.name}`); + } else { + console.log(`✅ Store already exists: ${storeConfig.name}`); + } + }); + }; + }); +} diff --git a/src/services/database.service.ts b/src/services/database.service.ts index 8281326..67e555b 100755 --- a/src/services/database.service.ts +++ b/src/services/database.service.ts @@ -1,10 +1,11 @@ import Services from './service'; +import { DATABASE_CONFIG } from './database-config'; export class Database { private static instance: Database; private db: IDBDatabase | null = null; - private dbName: string = '4nk'; - private dbVersion: number = 2; + private dbName: string = DATABASE_CONFIG.name; + private dbVersion: number = DATABASE_CONFIG.version; private serviceWorkerRegistration: ServiceWorkerRegistration | null = null; private messageChannel: MessageChannel | null = null; private messageChannelForGet: MessageChannel | null = null; @@ -49,6 +50,11 @@ export class Database { options: {}, indices: [], }, + AnkPBKDF2Keys: { + name: 'pbkdf2keys', + options: {}, + indices: [], + }, }; // Private constructor to prevent direct instantiation from outside diff --git a/src/services/event-bus.ts b/src/services/event-bus.ts index 95aa394..0c964c2 100644 --- a/src/services/event-bus.ts +++ b/src/services/event-bus.ts @@ -36,7 +36,7 @@ export class EventBus { * Ajoute un Ă©couteur d'Ă©vĂ©nement */ on(event: string, listener: EventListener): void { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} if (!this.listeners.has(event)) { this.listeners.set(event, []); @@ -60,7 +60,7 @@ export class EventBus { * Ajoute un Ă©couteur d'Ă©vĂ©nement qui ne se dĂ©clenche qu'une fois */ once(event: string, listener: EventListener): void { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} const onceListener = (data?: EventData) => { listener(data); @@ -74,10 +74,10 @@ export class EventBus { * Supprime un Ă©couteur d'Ă©vĂ©nement */ off(event: string, listener: EventListener): void { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} const eventListeners = this.listeners.get(event); - if (!eventListeners) return; + if (!eventListeners) {return;} const index = eventListeners.indexOf(listener); if (index > -1) { @@ -94,10 +94,10 @@ export class EventBus { * Émet un Ă©vĂ©nement */ emit(event: string, data?: EventData): void { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} const eventListeners = this.listeners.get(event); - if (!eventListeners || eventListeners.length === 0) return; + if (!eventListeners || eventListeners.length === 0) {return;} // CrĂ©er une copie pour Ă©viter les modifications pendant l'itĂ©ration const listeners = [...eventListeners]; @@ -115,10 +115,10 @@ export class EventBus { * Émet un Ă©vĂ©nement de maniĂšre asynchrone */ async emitAsync(event: string, data?: EventData): Promise { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} const eventListeners = this.listeners.get(event); - if (!eventListeners || eventListeners.length === 0) return; + if (!eventListeners || eventListeners.length === 0) {return;} const listeners = [...eventListeners]; const promises = listeners.map(listener => { @@ -138,7 +138,7 @@ export class EventBus { * Supprime tous les Ă©couteurs d'un Ă©vĂ©nement ou tous les Ă©couteurs */ removeAllListeners(event?: string): void { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} if (event) { this.listeners.delete(event); @@ -191,7 +191,7 @@ export class EventBus { * Nettoie les Ă©couteurs orphelins */ cleanup(): void { - if (this.isDestroyed) return; + if (this.isDestroyed) {return;} // Supprimer les Ă©couteurs qui ne sont plus dans les souscriptions this.listeners.forEach((listeners, event) => { diff --git a/src/services/memory-manager.ts b/src/services/memory-manager.ts index 198e5f6..9484463 100644 --- a/src/services/memory-manager.ts +++ b/src/services/memory-manager.ts @@ -41,7 +41,7 @@ export class MemoryManager { * DĂ©marre le monitoring de la mĂ©moire */ startMonitoring(): void { - if (this.isMonitoring) return; + if (this.isMonitoring) {return;} this.isMonitoring = true; this.logMemoryStats(); @@ -92,10 +92,10 @@ export class MemoryManager { */ getCache(cacheName: string, key: string): T | null { const cache = this.caches.get(cacheName); - if (!cache) return null; + if (!cache) {return null;} const entry = cache.get(key); - if (!entry) return null; + if (!entry) {return null;} // VĂ©rifier l'Ăąge de l'entrĂ©e if (Date.now() - entry.timestamp > this.maxCacheAge) { @@ -115,7 +115,7 @@ export class MemoryManager { */ deleteCache(cacheName: string, key: string): boolean { const cache = this.caches.get(cacheName); - if (!cache) return false; + if (!cache) {return false;} return cache.delete(key); } @@ -142,7 +142,7 @@ export class MemoryManager { */ getCacheStats(cacheName: string): { size: number; entries: any[] } { const cache = this.caches.get(cacheName); - if (!cache) return { size: 0, entries: [] }; + if (!cache) {return { size: 0, entries: [] };} const entries = Array.from(cache.entries()).map(([key, entry]) => ({ key, @@ -162,7 +162,7 @@ export class MemoryManager { */ getMemoryStats(): MemoryStats | null { const memory = (performance as any).memory; - if (!memory) return null; + if (!memory) {return null;} return { usedJSHeapSize: memory.usedJSHeapSize, @@ -177,7 +177,7 @@ export class MemoryManager { */ private checkMemoryUsage(): void { const stats = this.getMemoryStats(); - if (!stats) return; + if (!stats) {return;} if (stats.usedJSHeapSize > this.memoryThreshold) { this.performMemoryCleanup(); @@ -226,7 +226,7 @@ export class MemoryManager { */ private evictLeastUsedEntries(): void { this.caches.forEach(cache => { - if (cache.size <= this.maxCacheSize) return; + if (cache.size <= this.maxCacheSize) {return;} // Trier par nombre d'accĂšs et derniĂšre utilisation const entries = Array.from(cache.entries()).sort((a, b) => { diff --git a/src/services/modal.service.ts b/src/services/modal.service.ts index 2706fb0..0aed620 100755 --- a/src/services/modal.service.ts +++ b/src/services/modal.service.ts @@ -37,9 +37,9 @@ export default class ModalService { let html = modalHtml; html = html.replace('{{device1}}', myAddress); html = html.replace('{{device2}}', receiverAddress); - if (container) container.innerHTML += html; + if (container) {container.innerHTML += html;} const modal = document.getElementById('login-modal'); - if (modal) modal.style.display = 'flex'; + if (modal) {modal.style.display = 'flex';} const newScript = document.createElement('script'); newScript.setAttribute('type', 'module'); @@ -117,7 +117,7 @@ export default class ModalService { // Modal injection methods removed - using confirmation modal instead this.modal = document.getElementById('confirmation-modal'); - if (this.modal) this.modal.style.display = 'flex'; + if (this.modal) {this.modal.style.display = 'flex';} // Close modal when clicking outside of it window.onclick = event => { @@ -130,7 +130,7 @@ export default class ModalService { console.log('=============> Confirm Login'); } async closeLoginModal() { - if (this.modal) this.modal.style.display = 'none'; + if (this.modal) {this.modal.style.display = 'none';} } async showConfirmationModal( @@ -190,6 +190,6 @@ export default class ModalService { async closeConfirmationModal() { const service = await Services.getInstance(); await service.unpairDevice(); - if (this.modal) this.modal.style.display = 'none'; + if (this.modal) {this.modal.style.display = 'none';} } } diff --git a/src/services/pairing.service.ts b/src/services/pairing.service.ts index 5c80927..10eb0a0 100644 --- a/src/services/pairing.service.ts +++ b/src/services/pairing.service.ts @@ -151,7 +151,7 @@ export class PairingService { async generatePairingWords(): Promise { try { const device = await this.deviceRepo.getDevice(); - if (!device || !device.sp_wallet?.address) { + if (!device?.sp_wallet?.address) { throw new Error('No device address found'); } diff --git a/src/services/performance-monitor.ts b/src/services/performance-monitor.ts index 743e71c..89d811c 100644 --- a/src/services/performance-monitor.ts +++ b/src/services/performance-monitor.ts @@ -51,7 +51,7 @@ export class PerformanceMonitor { * DĂ©marre le monitoring des performances */ startMonitoring(): void { - if (this.isMonitoring) return; + if (this.isMonitoring) {return;} this.isMonitoring = true; console.log('📊 Performance monitoring started'); @@ -73,7 +73,7 @@ export class PerformanceMonitor { recordMetric(name: string, value: number, unit: string = 'ms'): void { // Use unit parameter console.log('Performance metric unit:', unit); - if (!this.isMonitoring) return; + if (!this.isMonitoring) {return;} if (!this.metrics.has(name)) { this.metrics.set(name, []); @@ -130,7 +130,7 @@ export class PerformanceMonitor { */ getMetricStats(name: string): PerformanceStats | null { const values = this.metrics.get(name); - if (!values || values.length === 0) return null; + if (!values || values.length === 0) {return null;} const sorted = [...values].sort((a, b) => a - b); const sum = values.reduce((acc, val) => acc + val, 0); @@ -173,7 +173,7 @@ export class PerformanceMonitor { // Analyser chaque mĂ©trique Object.entries(metrics).forEach(([name, stats]) => { const threshold = this.thresholds[name]; - if (!threshold) return; + if (!threshold) {return;} const percentage = (stats.average / threshold.critical) * 100; healthScore -= Math.max(0, 100 - percentage); @@ -217,7 +217,7 @@ export class PerformanceMonitor { */ private checkThresholds(name: string, value: number): void { const threshold = this.thresholds[name]; - if (!threshold) return; + if (!threshold) {return;} if (value > threshold.critical) { console.warn(`🚹 Critical performance threshold exceeded for ${name}: ${value}ms`); @@ -230,7 +230,7 @@ export class PerformanceMonitor { * Configure les observateurs de performance */ private setupPerformanceObservers(): void { - if (!window.PerformanceObserver) return; + if (!window.PerformanceObserver) {return;} // Observer les mesures de performance const measureObserver = new PerformanceObserver((list) => { diff --git a/src/services/secure-credentials.service.ts b/src/services/secure-credentials.service.ts index f5472a1..26d70eb 100644 --- a/src/services/secure-credentials.service.ts +++ b/src/services/secure-credentials.service.ts @@ -110,8 +110,8 @@ export class SecureCredentialsService { switch (securityMode) { case 'proton-pass': case 'os': - // RĂ©cupĂ©rer la clĂ© chiffrĂ©e avec WebAuthn (rĂ©cupĂ©ration dynamique) - return await webAuthnService.retrieveKeyWithWebAuthn(); + // RĂ©cupĂ©rer la clĂ© chiffrĂ©e avec WebAuthn + return await webAuthnService.retrieveKeyWithWebAuthn(securityMode); case 'browser': // RĂ©cupĂ©rer la clĂ© du gestionnaire de mots de passe diff --git a/src/services/secure-key-manager.ts b/src/services/secure-key-manager.ts index fecb54f..6f65b29 100644 --- a/src/services/secure-key-manager.ts +++ b/src/services/secure-key-manager.ts @@ -65,7 +65,7 @@ export class SecureKeyManager { async getPrivateKey(password: string): Promise { try { const encryptedData = await this.getEncryptedData(); - if (!encryptedData) return null; + if (!encryptedData) {return null;} const derivedKey = await this.deriveKey(password); const iv = encryptedData.slice(0, 12); diff --git a/src/services/secure-logger.ts b/src/services/secure-logger.ts index 88f68cc..0ffc4fe 100644 --- a/src/services/secure-logger.ts +++ b/src/services/secure-logger.ts @@ -91,7 +91,7 @@ export class SecureLogger { * Sanitise le contexte pour supprimer les donnĂ©es sensibles */ private sanitizeContext(context?: LogContext): LogContext | undefined { - if (!context) return undefined; + if (!context) {return undefined;} const sanitized: LogContext = {}; const sensitiveKeys = [ diff --git a/src/services/security-mode.service.ts b/src/services/security-mode.service.ts index 122ecd4..164f567 100644 --- a/src/services/security-mode.service.ts +++ b/src/services/security-mode.service.ts @@ -1,10 +1,10 @@ /** * SecurityModeService - Gestion des modes de sĂ©curisation * GĂšre le stockage et la rĂ©cupĂ©ration du mode de sĂ©curisation choisi par l'utilisateur + * NOTE: Le mode de sĂ©curitĂ© est stockĂ© uniquement avec la clĂ© PBKDF2, pas dans un store sĂ©parĂ© */ import { secureLogger } from './secure-logger'; -import Database from './database.service'; export type SecurityMode = 'proton-pass' | 'os' | 'browser' | 'otp' | '2fa' | 'password' | 'none'; @@ -25,11 +25,10 @@ export interface SecurityModeConfig { export class SecurityModeService { private static instance: SecurityModeService; - private database: Database; private currentMode: SecurityMode | null = null; private constructor() { - this.database = new Database(); + // N'instancier pas Database ici, il sera rĂ©cupĂ©rĂ© via getInstance() quand nĂ©cessaire } public static getInstance(): SecurityModeService { @@ -41,78 +40,32 @@ export class SecurityModeService { /** * RĂ©cupĂšre le mode de sĂ©curisation actuel + * NOTE: Le mode est stockĂ© uniquement en mĂ©moire, dĂ©rivĂ© de la clĂ© PBKDF2 stockĂ©e */ public async getCurrentMode(): Promise { if (this.currentMode) { return this.currentMode; } - try { - // VĂ©rifier que la base de donnĂ©es est disponible - if (!this.database || typeof this.database.getObject !== 'function') { - secureLogger.warn('Database not available, returning null mode', { - component: 'SecurityModeService', - operation: 'getCurrentMode' - }); - return null; - } - - const storedMode = await this.database.getObject('security_settings', 'current_mode'); - this.currentMode = storedMode?.mode || null; - - secureLogger.info('Current security mode retrieved', { - component: 'SecurityModeService', - operation: 'getCurrentMode', - mode: this.currentMode - }); - - return this.currentMode; - } catch (error) { - // Si l'erreur est "object store not found", c'est normal pour un premier lancement - if (error instanceof Error && (error.name === 'NotFoundError' || error.message.includes('object stores was not found'))) { - secureLogger.info('No security mode set yet (first launch)', { - component: 'SecurityModeService', - operation: 'getCurrentMode' - }); - return null; - } - - secureLogger.error('Failed to retrieve current security mode', error as Error, { - component: 'SecurityModeService', - operation: 'getCurrentMode' - }); - return null; - } + // Le mode de sĂ©curitĂ© n'est pas stockĂ© en base, il est dĂ©rivĂ© de la clĂ© PBKDF2 + // Ici on retourne null si aucun mode n'est en mĂ©moire + // Le mode sera dĂ©fini lors de la gĂ©nĂ©ration de la clĂ© PBKDF2 + secureLogger.info('No security mode in memory (will be set when PBKDF2 key is generated)', { + component: 'SecurityModeService', + operation: 'getCurrentMode' + }); + return null; } /** * DĂ©finit le mode de sĂ©curisation + * NOTE: Le mode de sĂ©curitĂ© est stockĂ© uniquement avec la clĂ© PBKDF2, pas dans un store sĂ©parĂ© */ public async setSecurityMode(mode: SecurityMode): Promise { try { const modeConfig = this.getSecurityModeConfig(mode); - // VĂ©rifier que la base de donnĂ©es est disponible - if (!this.database || typeof this.database.setObject !== 'function') { - secureLogger.warn('Database not available, setting mode in memory only', { - component: 'SecurityModeService', - operation: 'setSecurityMode', - mode - }); - this.currentMode = mode; - return; - } - - // Stocker le mode en base - await this.database.setObject('security_settings', 'current_mode', { - mode, - name: modeConfig.name, - description: modeConfig.description, - securityLevel: modeConfig.securityLevel, - timestamp: Date.now(), - implementation: modeConfig.implementation - }); - + // Stocker uniquement en mĂ©moire this.currentMode = mode; secureLogger.info('Security mode set successfully', { @@ -133,13 +86,8 @@ export class SecurityModeService { operation: 'setSecurityMode', mode }); - // En cas d'erreur, dĂ©finir le mode en mĂ©moire seulement - this.currentMode = mode; - secureLogger.warn('Security mode set in memory only due to database error', { - component: 'SecurityModeService', - operation: 'setSecurityMode', - mode - }); + // NE PAS FAIRE DE FALLBACK - Ă©chouer complĂštement + throw error; } } @@ -286,7 +234,7 @@ export class SecurityModeService { */ public async isCurrentModeSecure(): Promise { const currentMode = await this.getCurrentMode(); - if (!currentMode) return false; + if (!currentMode) {return false;} const config = this.getSecurityModeConfig(currentMode); return config.securityLevel === 'high' || config.securityLevel === 'medium'; @@ -350,7 +298,8 @@ export class SecurityModeService { */ public async resetSecurityMode(): Promise { try { - await this.database.deleteObject('security_settings', 'current_mode'); + const database = await Database.getInstance(); + await database.deleteObject('security_settings', 'current_mode'); this.currentMode = null; secureLogger.info('Security mode reset', { @@ -371,7 +320,8 @@ export class SecurityModeService { */ public async getSecurityModeHistory(): Promise { try { - const history = await this.database.getObject('security_settings', 'mode_history') || []; + const database = await Database.getInstance(); + const history = await database.getObject('security_settings', 'mode_history') || []; return history; } catch (error) { secureLogger.error('Failed to retrieve security mode history', error as Error, { @@ -399,7 +349,12 @@ export class SecurityModeService { history.splice(10); } - await this.database.setObject('security_settings', 'mode_history', history); + const database = await Database.getInstance(); + await database.addObject({ + storeName: 'security_settings', + key: 'mode_history', + object: history + }); } catch (error) { secureLogger.error('Failed to add to security mode history', error as Error, { component: 'SecurityModeService', diff --git a/src/services/service.ts b/src/services/service.ts index 0f7fb76..7f75784 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -448,7 +448,7 @@ export default class Services { // Use processId parameter console.log('Getting cached process:', processId); const process = this.processesCache[processId]; - if (!process) return null; + if (!process) {return null;} const now = Date.now(); if (now - (process as any).timestamp > this.cacheExpiry) { @@ -704,7 +704,7 @@ export default class Services { if (!roles) { throw new Error('No roles found'); } - let members: Set = new Set(); + const members: Set = new Set(); for (const role of Object.values(roles!)) { for (const member of role.members) { // Check if we know the member that matches this id @@ -726,7 +726,7 @@ export default class Services { } // If pairedAddresses is not in the current state, look in previous states - if (!publicData || !publicData['pairedAddresses']) { + if (!publicData?.['pairedAddresses']) { // Look for pairedAddresses in previous states for (let i = process.states.length - 1; i >= 0; i--) { const state = process.states[i]; @@ -737,7 +737,7 @@ export default class Services { } } - if (!publicData || !publicData['pairedAddresses']) { + if (!publicData?.['pairedAddresses']) { throw new Error('Not a pairing process'); } const decodedAddresses = this.decodeValue(publicData['pairedAddresses']); @@ -749,14 +749,14 @@ export default class Services { // Ensure the amount is available before proceeding await this.getTokensFromFaucet(); - let unconnectedAddresses = new Set(); + const unconnectedAddresses = new Set(); const myAddress = this.getDeviceAddress(); for (const member of Array.from(members)) { const sp_addresses = member.sp_addresses; - if (!sp_addresses || sp_addresses.length === 0) continue; + if (!sp_addresses || sp_addresses.length === 0) {continue;} for (const address of sp_addresses) { // For now, we ignore our own device address, although there might be use cases for having a secret with ourselves - if (address === myAddress) continue; + if (address === myAddress) {continue;} if ((await this.getSecretForAddress(address)) === null) { unconnectedAddresses.add(address); } @@ -1271,7 +1271,7 @@ export default class Services { try { // Get current device to check birthday const device = await this.getDeviceFromDatabase(); - if (device && device.sp_wallet) { + if (device?.sp_wallet) { console.log('🔍 Device wallet state:', { birthday: device.sp_wallet.birthday, last_scan: device.sp_wallet.last_scan, @@ -1332,7 +1332,7 @@ export default class Services { // Check wallet state in SDK try { const device = await this.getDeviceFromDatabase(); - if (device && device.sp_wallet) { + if (device?.sp_wallet) { console.log('🔍 Wallet state:'); console.log('- Last scan:', device.sp_wallet.last_scan); console.log('- Current block:', this.currentBlockHeight); @@ -1719,7 +1719,7 @@ export default class Services { private ensureWalletKeysAvailable(): void { try { const device = this.dumpDeviceFromMemory(); - if (!device || !device.sp_wallet) { + if (!device?.sp_wallet) { throw new Error('❌ Wallet not initialized - WebAuthn decryption required'); } @@ -1749,7 +1749,7 @@ export default class Services { // Additional debugging: check wallet state try { const device = this.dumpDeviceFromMemory(); - if (device && device.sp_wallet) { + if (device?.sp_wallet) { console.log(`🔍 Wallet debugging info:`, { birthday: device.sp_wallet.birthday, last_scan: device.sp_wallet.last_scan, @@ -2021,7 +2021,7 @@ export default class Services { try { const device = await this.getDeviceFromDatabase(); - if (!device || !device.sp_wallet) { + if (!device?.sp_wallet) { throw new Error('Device not found or wallet not initialized'); } @@ -2600,14 +2600,14 @@ export default class Services { const existing = await this.getProcess(processId); if (existing) { // Look for state id we don't know yet - let newStates: string[] = []; - let newRoles: Record[] = []; + const newStates: string[] = []; + const newRoles: Record[] = []; if (!Array.isArray(process.states)) { console.warn('process.states is not an array:', process.states); continue; } for (const state of process.states) { - if (!state || !state.state_id) { + if (!state?.state_id) { continue; } // shouldn't happen if (state.state_id === EMPTY32BYTES) { @@ -2944,7 +2944,7 @@ export default class Services { } public getLastCommitedState(process: Process): ProcessState | null { - if (process.states.length === 0) return null; + if (process.states.length === 0) {return null;} const processTip = process.states[process.states.length - 1].commited_in; const lastCommitedState = process.states.findLast(state => state.commited_in !== processTip); if (lastCommitedState) { @@ -2955,7 +2955,7 @@ export default class Services { } public getLastCommitedStateIndex(process: Process): number | null { - if (process.states.length === 0) return null; + if (process.states.length === 0) {return null;} const processTip = process.states[process.states.length - 1].commited_in; for (let i = process.states.length - 1; i >= 0; i--) { if (process.states[i].commited_in !== processTip) { @@ -2966,14 +2966,14 @@ export default class Services { } public getUncommitedStates(process: Process): ProcessState[] { - if (process.states.length === 0) return []; + if (process.states.length === 0) {return [];} const processTip = process.states[process.states.length - 1].commited_in; const res = process.states.filter(state => state.commited_in === processTip); return res.filter(state => state.state_id !== EMPTY32BYTES); } public getStateFromId(process: Process, stateId: string): ProcessState | null { - if (process.states.length === 0) return null; + if (process.states.length === 0) {return null;} const state = process.states.find(state => state.state_id === stateId); if (state) { return state; @@ -2983,7 +2983,7 @@ export default class Services { } public getNextStateAfterId(process: Process, stateId: string): ProcessState | null { - if (process.states.length === 0) return null; + if (process.states.length === 0) {return null;} const index = process.states.findIndex(state => state.state_id === stateId); diff --git a/src/services/websocket-manager.ts b/src/services/websocket-manager.ts index 1277b0a..61894b7 100644 --- a/src/services/websocket-manager.ts +++ b/src/services/websocket-manager.ts @@ -182,7 +182,7 @@ export class WebSocketManager { * Configure les Ă©couteurs d'Ă©vĂ©nements WebSocket */ private setupEventListeners(resolve: Function, _reject: Function): void { - if (!this.ws) return; + if (!this.ws) {return;} this.ws.onopen = () => { this.status.isConnected = true; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 515c9d2..3754e12 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -63,7 +63,7 @@ class Logger { } private log(level: LogLevel, message: string, context?: LogContext): void { - if (!this.shouldLog(level)) return; + if (!this.shouldLog(level)) {return;} const entry = this.createLogEntry(level, message, context); this.logs.push(entry); diff --git a/src/utils/sp-address.utils.ts b/src/utils/sp-address.utils.ts index 3d19edb..ebba5f8 100755 --- a/src/utils/sp-address.utils.ts +++ b/src/utils/sp-address.utils.ts @@ -2344,11 +2344,11 @@ function showLoadingState() { // Update loading message const loadingText = loadingFlow.querySelector('h2'); const loadingDesc = loadingFlow.querySelector('p'); - if (loadingText) loadingText.textContent = 'Initializing...'; - if (loadingDesc) loadingDesc.textContent = 'Setting up secure pairing interface'; + if (loadingText) {loadingText.textContent = 'Initializing...';} + if (loadingDesc) {loadingDesc.textContent = 'Setting up secure pairing interface';} } - if (creatorFlow) (creatorFlow as HTMLElement).style.display = 'none'; - if (joinerFlow) (joinerFlow as HTMLElement).style.display = 'none'; + if (creatorFlow) {(creatorFlow as HTMLElement).style.display = 'none';} + if (joinerFlow) {(joinerFlow as HTMLElement).style.display = 'none';} } // Detect flow and show appropriate interface @@ -2364,16 +2364,16 @@ async function detectAndShowFlow() { if (hasWords) { // Joiner flow - if (loadingFlow) (loadingFlow as HTMLElement).style.display = 'none'; - if (creatorFlow) (creatorFlow as HTMLElement).style.display = 'none'; - if (joinerFlow) (joinerFlow as HTMLElement).style.display = 'block'; + if (loadingFlow) {(loadingFlow as HTMLElement).style.display = 'none';} + if (creatorFlow) {(creatorFlow as HTMLElement).style.display = 'none';} + if (joinerFlow) {(joinerFlow as HTMLElement).style.display = 'block';} updateJoinerStatus('Ready to join pairing'); } else { // Creator flow - if (loadingFlow) (loadingFlow as HTMLElement).style.display = 'none'; - if (creatorFlow) (creatorFlow as HTMLElement).style.display = 'block'; - if (joinerFlow) (joinerFlow as HTMLElement).style.display = 'none'; + if (loadingFlow) {(loadingFlow as HTMLElement).style.display = 'none';} + if (creatorFlow) {(creatorFlow as HTMLElement).style.display = 'block';} + if (joinerFlow) {(joinerFlow as HTMLElement).style.display = 'none';} updateCreatorStatus('Ready to create pairing'); } diff --git a/src/utils/subscription.utils.ts b/src/utils/subscription.utils.ts index 1a34143..8579ef3 100755 --- a/src/utils/subscription.utils.ts +++ b/src/utils/subscription.utils.ts @@ -21,7 +21,7 @@ export function addSubscription( event: any, eventHandler: EventListenerOrEventListenerObject ): void { - if (!element) return; + if (!element) {return;} subscriptions.push({ element, event, eventHandler }); element.addEventListener(event, eventHandler); } diff --git a/src/websockets.ts b/src/websockets.ts index fc2c6a5..e4d4dcc 100755 --- a/src/websockets.ts +++ b/src/websockets.ts @@ -4,7 +4,7 @@ import { messageValidator } from './services/message-validator'; import { secureLogger } from './services/secure-logger'; let ws: WebSocket; -let messageQueue: string[] = []; +const messageQueue: string[] = []; export async function initWebsocket(url: string) { ws = new WebSocket(url);