From c2bd615e88fe803f91884ea24177067f02a8b4f3 Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Tue, 28 Oct 2025 13:30:45 +0100 Subject: [PATCH] fix: resolve 'Device not found' error in birthday-setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivations :** - L'erreur 'Device not found' se produit lors de la mise à jour de la date anniversaire - Le wallet est bien créé et sauvegardé mais n'est pas trouvé lors de la récupération - Problème d'incohérence entre l'accès direct à IndexedDB et l'accès via service worker **Modifications :** - Ajout de logs de débogage dans getDeviceFromDatabase pour tracer le problème - Modification de getDeviceFromDatabase pour utiliser directement IndexedDB au lieu du service worker - Correction des erreurs TypeScript dans birthday-setup.ts et home.ts - Correction des comparaisons BigInt dans service.ts - Modification de birthday-setup.ts pour éviter l'utilisation de méthodes privées **Pages affectées :** - src/services/service.ts (getDeviceFromDatabase, updateDeviceBlockHeight) - src/services/database.service.ts (getObject) - src/pages/birthday-setup/birthday-setup.ts - src/pages/home/home.ts - src/components/security-mode-selector/security-mode-selector.ts --- .cursor/rules/init.mdc | 5 + IA_agents/all.md | 4 +- docs/INITIALIZATION_FLOW.md | 393 ++++++++++++++++ .../security-mode-selector.ts | 2 +- src/pages/birthday-setup/birthday-setup.ts | 11 +- src/pages/home/home.ts | 17 +- src/pages/security-setup/security-setup.ts | 7 +- src/pages/wallet-setup/wallet-setup.ts | 45 +- src/router.ts | 15 +- src/services/credentials/storage.service.ts | 175 +------ src/services/credentials/webauthn.service.ts | 47 +- src/services/database.service.ts | 23 +- src/services/secure-credentials.service.ts | 435 +++++++++++++++++- src/services/service.ts | 49 +- test-birthday-setup.html | 144 ++++++ 15 files changed, 1108 insertions(+), 264 deletions(-) create mode 100644 .cursor/rules/init.mdc create mode 100644 docs/INITIALIZATION_FLOW.md create mode 100644 test-birthday-setup.html diff --git a/.cursor/rules/init.mdc b/.cursor/rules/init.mdc new file mode 100644 index 0000000..b83e7d8 --- /dev/null +++ b/.cursor/rules/init.mdc @@ -0,0 +1,5 @@ +--- +alwaysApply: true +--- + +lire avec attention: docs/INITIALIZATION_FLOW.md diff --git a/IA_agents/all.md b/IA_agents/all.md index 6ed97e9..68e8f36 100644 --- a/IA_agents/all.md +++ b/IA_agents/all.md @@ -1,8 +1,8 @@ # Lecoffre -voir les fichiers README.md +voir README.md -## Instructions for Claude +voir docs/INITIALIZATION_FLOW.md ### General diff --git a/docs/INITIALIZATION_FLOW.md b/docs/INITIALIZATION_FLOW.md new file mode 100644 index 0000000..a505f4d --- /dev/null +++ b/docs/INITIALIZATION_FLOW.md @@ -0,0 +1,393 @@ +# Documentation de l'Initialisation LeCoffre.io + +## Vue d'ensemble + +Le système LeCoffre.io suit un processus d'initialisation en plusieurs étapes pour créer et sécuriser un wallet Bitcoin. Ce document détaille chaque étape du processus, depuis le choix du mode de sécurité jusqu'au pairing réussi et à la récupération des processus. + +## Architecture des Stores IndexedDB + +### Stores utilisés : +- **`pbkdf2keys`** : Stockage des clés PBKDF2 chiffrées par mode de sécurité +- **`wallet`** : Stockage du wallet chiffré (device + wallet data) +- **`credentials`** : Stockage des credentials de pairing (utilisé uniquement après pairing) +- **`processes`** : Stockage des processus de communication +- **`labels`** : Stockage des labels de transactions +- **`shared_secrets`** : Stockage des secrets partagés +- **`unconfirmed_secrets`** : Stockage des secrets non confirmés +- **`diffs`** : Stockage des différences de synchronisation +- **`data`** : Stockage des données générales + +## Flux d'Initialisation Complet + +### 1. Démarrage de l'Application + +**Fichier :** `src/router.ts` → `checkStorageStateAndNavigate()` + +L'application vérifie l'état du storage pour déterminer l'étape suivante : + +```typescript +// Logique de progression : +// - Si pairing → account +// - Si date anniversaire → pairing +// - Si wallet → birthday-setup +// - Si pbkdf2 → wallet-setup +// - Sinon → security-setup +``` + +**États possibles :** +1. **Appareil appairé** → Redirection vers `account` +2. **Date anniversaire configurée** → Redirection vers `home` (pairing) +3. **Wallet existe sans date anniversaire** → Redirection vers `birthday-setup` +4. **Clé PBKDF2 existe** → Redirection vers `wallet-setup` +5. **Aucune configuration** → Redirection vers `security-setup` + +### 2. Configuration du Mode de Sécurité + +**Fichier :** `src/pages/security-setup/security-setup.ts` + +#### 2.1 Sélection du Mode + +L'utilisateur choisit parmi les modes disponibles : + +| Mode | Nom | Description | Niveau de Sécurité | Clé de Chiffrement PBKDF2 | Stockage de la Clé de Chiffrement | +|------|-----|-------------|-------------------|---------------------------|-----------------------------------| +| `proton-pass` | Proton Pass | Authentification biométrique via Proton Pass | High | Clé WebAuthn générée par le navigateur | Stockée dans le navigateur (WebAuthn credential) | +| `os` | Authentificateur OS | Authentification biométrique du système | High | Clé WebAuthn générée par le système | Stockée dans le système d'exploitation | +| `otp` | OTP | Code à usage unique (Google Authenticator, etc.) | High | Aucune (clé PBKDF2 stockée en clair) | Secret OTP stocké dans l'application OTP | +| `password` | Mot de passe | Chiffrement par mot de passe (non sauvegardé) | Low | Mot de passe utilisateur | Stocké dans le gestionnaire de mots de passe du navigateur | +| `none` | Aucune sécurité | Chiffrement avec clé en dur (non recommandé) | Critical | Clé en dur `4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE` | Intégrée dans le code (non sécurisé) | + +#### 2.2 Génération de la Clé PBKDF2 + +**Fichier :** `src/services/secure-credentials.service.ts` → `generatePBKDF2Key()` + +Pour chaque mode, une clé PBKDF2 est générée et stockée différemment : + +##### Mode `proton-pass` et `os` (WebAuthn) +```typescript +// Stockage avec WebAuthn (authentification biométrique) +await webAuthnService.storeKeyWithWebAuthn(pbkdf2Key, securityMode); +``` +- **Store :** `pbkdf2keys` +- **Clé :** `security_mode` (ex: "proton-pass") +- **Valeur :** Clé PBKDF2 chiffrée avec WebAuthn +- **Authentification :** Biométrique (empreinte, visage, etc.) + +##### Mode `otp` +```typescript +// Génération du secret OTP +const otpSecret = await this.generateOTPSecret(); +// Stockage de la clé PBKDF2 en clair +await this.storePBKDF2KeyInStore(pbkdf2Key, securityMode); +// Affichage du QR code +this.displayOTPQRCode(otpSecret); +``` +- **Store :** `pbkdf2keys` +- **Clé :** `security_mode` ("otp") +- **Valeur :** Clé PBKDF2 en clair +- **Authentification :** Code OTP généré par l'application + +##### Mode `password` +```typescript +// Demande du mot de passe utilisateur +const userPassword = await this.promptForPasswordWithBrowser(); +// Chiffrement de la clé PBKDF2 +const encryptedKey = await encryptionService.encrypt(pbkdf2Key, userPassword); +// Stockage chiffré +await this.storePBKDF2KeyInStore(encryptedKey, securityMode); +``` +- **Store :** `pbkdf2keys` +- **Clé :** `security_mode` ("password") +- **Valeur :** Clé PBKDF2 chiffrée avec le mot de passe utilisateur +- **Authentification :** Mot de passe utilisateur (non sauvegardé) + +##### Mode `none` +```typescript +// Clé de chiffrement en dur +const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE'; +// Chiffrement avec la clé en dur +const encryptedKeyNone = await encryptionService.encrypt(pbkdf2Key, hardcodedKey); +// Stockage chiffré +await this.storePBKDF2KeyInStore(encryptedKeyNone, securityMode); +``` +- **Store :** `pbkdf2keys` +- **Clé :** `security_mode` ("none") +- **Valeur :** Clé PBKDF2 chiffrée avec clé en dur +- **Authentification :** Aucune (non sécurisé) + +### 3. Création du Wallet + +**Fichier :** `src/pages/wallet-setup/wallet-setup.ts` + +#### 3.1 Récupération de la Clé PBKDF2 + +Le système teste tous les modes de sécurité pour trouver la clé PBKDF2 valide : + +```typescript +const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass']; +for (const mode of allSecurityModes) { + const hasKey = await secureCredentialsService.hasPBKDF2Key(mode); + if (hasKey) { + const key = await secureCredentialsService.retrievePBKDF2Key(mode); + if (key) { + currentMode = mode; + break; + } + } +} +``` + +#### 3.2 Génération des Clés du Wallet + +```typescript +// Génération des clés temporaires +const walletData = { + scan_sk: encryptionService.generateRandomKey(), + spend_key: encryptionService.generateRandomKey(), + network: 'signet', + state: 'birthday_waiting', + created_at: new Date().toISOString() +}; +``` + +#### 3.3 Création du Device via SDK + +```typescript +// Création du device avec birthday = 0 +const spAddress = await services.sdkClient.create_new_device(0, 'signet'); +// Génération forcée du wallet +const wallet = await services.sdkClient.dump_wallet(); +``` + +#### 3.4 Stockage Chiffré du Wallet + +```typescript +// Chiffrement du device +const encryptedDevice = await encryptionService.encrypt(deviceString, pbkdf2Key); +// Chiffrement du wallet +const encryptedWallet = await encryptionService.encrypt(walletString, pbkdf2Key); + +// Stockage dans le store wallet +const walletObject = { + pre_id: '1', + encrypted_device: encryptedDevice, + encrypted_wallet: encryptedWallet +}; +``` + +**Store :** `wallet` +**Clé :** `pre_id` ("1") +**Valeur :** Objet contenant uniquement des données chiffrées + +### 4. Configuration de la Date Anniversaire + +**Fichier :** `src/pages/birthday-setup/birthday-setup.ts` + +#### 4.1 Connexion aux Relais + +```typescript +// Connexion aux relais Bitcoin +await services.connectToRelays(); +``` + +#### 4.2 Mise à Jour de la Date Anniversaire + +```typescript +// Récupération de la hauteur de bloc actuelle +const currentBlockHeight = await services.getCurrentBlockHeight(); +// Mise à jour du birthday du device +await services.updateDeviceBirthday(currentBlockHeight); +``` + +#### 4.3 Synchronisation des Processus + +```typescript +// Restauration des processus depuis la base de données +await services.restoreProcessesFromDB(); +``` + +### 5. Processus de Pairing + +**Fichier :** `src/pages/home/home.ts` → `handleMainPairing()` + +#### 5.1 Vérification du Mode de Sécurité + +```typescript +const currentMode = await securityModeService.getCurrentMode(); +if (!currentMode) { + // Afficher le sélecteur de mode de sécurité + await showSecurityModeSelector(); + return; +} +``` + +#### 5.2 Authentification selon le Mode + +##### Mode `proton-pass` et `os` +```typescript +// Authentification WebAuthn +const credential = await navigator.credentials.get({ + publicKey: { + challenge: new Uint8Array(32), + allowCredentials: [{ + id: credentialId, + type: 'public-key' + }] + } +}); +``` + +##### Mode `otp` +```typescript +// Demande du code OTP +const otpCode = await this.promptForOTPCode(); +// Vérification du code OTP +const isValid = await this.verifyOTPCode(otpCode, otpSecret); +``` + +##### Mode `password` +```typescript +// Demande du mot de passe +const password = await this.promptForPassword(); +// Déchiffrement de la clé PBKDF2 +const pbkdf2Key = await encryptionService.decrypt(encryptedKey, password); +``` + +##### Mode `none` +```typescript +// Déchiffrement avec la clé en dur +const pbkdf2Key = await encryptionService.decrypt(encryptedKey, hardcodedKey); +``` + +#### 5.3 Génération des Credentials de Pairing + +```typescript +// Génération des credentials sécurisés +const credentialData = await secureCredentialsService.generateSecureCredentials('4nk-secure-password'); + +// Stockage des credentials dans le store credentials +await secureCredentialsService.storeCredentials(credentialData, ''); + +// Récupération et déchiffrement des credentials +const retrievedCredentials = await secureCredentialsService.retrieveCredentials(''); +``` + +#### 5.4 Création du Processus de Pairing + +```typescript +// Création du processus via le SDK +const pairingResult = await services.createPairingProcess({ + spendKey: retrievedCredentials.spendKey, + scanKey: retrievedCredentials.scanKey +}); +``` + +### 6. Récupération des Processus + +**Fichier :** `src/services/service.ts` → `restoreProcessesFromDB()` + +#### 6.1 Synchronisation des Processus + +```typescript +// Récupération des processus depuis la base de données +const processes = await processRepo.getAllProcesses(); + +// Synchronisation avec le réseau +for (const process of processes) { + await services.syncProcess(process); +} +``` + +#### 6.2 Mise à Jour de l'État de Pairing + +```typescript +// Vérification de l'état de pairing +const isPaired = services.isPaired(); +if (isPaired) { + // Redirection vers la page account + await navigate('account'); +} +``` + +## Diagramme de Flux + +```mermaid +graph TD + A[Démarrage Application] --> B{Vérification État Storage} + B -->|Aucune config| C[Security Setup] + B -->|PBKDF2 existe| D[Wallet Setup] + B -->|Wallet existe| E[Birthday Setup] + B -->|Birthday configuré| F[Pairing] + B -->|Appareil appairé| G[Account] + + C --> C1[Sélection Mode Sécurité] + C1 --> C2[Génération Clé PBKDF2] + C2 --> C3[Stockage selon Mode] + C3 --> D + + D --> D1[Récupération Clé PBKDF2] + D1 --> D2[Création Device SDK] + D2 --> D3[Génération Wallet] + D3 --> D4[Stockage Chiffré] + D4 --> E + + E --> E1[Connexion Relais] + E1 --> E2[Mise à Jour Birthday] + E2 --> E3[Synchronisation Processus] + E3 --> F + + F --> F1[Authentification Mode] + F1 --> F2[Génération Credentials] + F2 --> F3[Création Processus Pairing] + F3 --> F4[Récupération Processus] + F4 --> G +``` + +## Sécurité par Mode + +### Mode `proton-pass` et `os` +- **Stockage :** Clé PBKDF2 chiffrée avec WebAuthn +- **Authentification :** Biométrique (empreinte, visage) +- **Sécurité :** Élevée (clé matérielle) + +### Mode `otp` +- **Stockage :** Clé PBKDF2 en clair +- **Authentification :** Code OTP temporaire +- **Sécurité :** Élevée (authentification à deux facteurs) + +### Mode `password` +- **Stockage :** Clé PBKDF2 chiffrée avec mot de passe utilisateur +- **Authentification :** Mot de passe utilisateur +- **Sécurité :** Faible (dépend de la force du mot de passe) + +### Mode `none` +- **Stockage :** Clé PBKDF2 chiffrée avec clé en dur +- **Authentification :** Aucune +- **Sécurité :** Critique (non recommandé) + +## Gestion des Erreurs + +### Erreurs de Chiffrement +- **Clé PBKDF2 introuvable** → Redirection vers `security-setup` +- **Échec de déchiffrement** → Demande de réauthentification +- **Wallet corrompu** → Recréation du wallet + +### Erreurs de Réseau +- **Connexion relais échouée** → Retry automatique +- **Synchronisation échouée** → Mode hors ligne +- **Pairing échoué** → Nouvelle tentative + +### Erreurs d'Authentification +- **WebAuthn échoué** → Fallback vers autre mode +- **OTP invalide** → Nouvelle demande +- **Mot de passe incorrect** → Nouvelle tentative + +## Points d'Attention + +1. **Ordre des modes testés** : `['none', 'otp', 'password', 'os', 'proton-pass']` +2. **Store `credentials`** : Utilisé uniquement après pairing +3. **Clé PBKDF2** : Toujours stockée dans `pbkdf2keys` avec `security_mode` comme clé +4. **Wallet** : Toujours stocké chiffré dans le store `wallet` +5. **Redirection automatique** : 3 secondes après création du wallet vers `birthday-setup` + +Cette documentation couvre l'ensemble du processus d'initialisation du système LeCoffre.io, depuis la configuration de sécurité jusqu'au pairing réussi et à la récupération des processus. diff --git a/src/components/security-mode-selector/security-mode-selector.ts b/src/components/security-mode-selector/security-mode-selector.ts index f62463a..b95abb2 100644 --- a/src/components/security-mode-selector/security-mode-selector.ts +++ b/src/components/security-mode-selector/security-mode-selector.ts @@ -222,7 +222,7 @@ export class SecurityModeSelector { this.container.addEventListener('click', (e) => { const option = (e.target as HTMLElement).closest('.security-option'); if (option) { - this.selectMode(option.dataset.mode as SecurityMode); + this.selectMode((option as HTMLElement).dataset.mode as SecurityMode); } }); diff --git a/src/pages/birthday-setup/birthday-setup.ts b/src/pages/birthday-setup/birthday-setup.ts index 6e28cff..c0a9fb5 100644 --- a/src/pages/birthday-setup/birthday-setup.ts +++ b/src/pages/birthday-setup/birthday-setup.ts @@ -41,7 +41,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 as Error).message); attempts++; if (attempts >= maxAttempts) { throw new Error(`Services failed to initialize after ${maxAttempts} attempts.`); @@ -50,6 +50,10 @@ document.addEventListener('DOMContentLoaded', async () => { } } + if (!services) { + throw new Error('Services not initialized'); + } + // Connexion aux relais await services.connectAllRelays(); console.log('✅ Relays connected successfully'); @@ -58,8 +62,7 @@ document.addEventListener('DOMContentLoaded', async () => { updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading'); updateProgress(40); - // Attendre que les relais soient prêts - await services.getRelayReadyPromise(); + // Les relais sont déjà prêts après connectAllRelays console.log('✅ Communication handshake completed'); // Mettre à jour la date anniversaire du wallet @@ -103,7 +106,7 @@ document.addEventListener('DOMContentLoaded', async () => { } // Gestion du bouton continuer - continueBtn.addEventListener('click', () => { + continueBtn.addEventListener('click', async () => { console.log('🏠 Redirecting to main application...'); // Rediriger vers l'application principale console.log('🎂 Birthday setup completed, checking storage state...'); diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index 9d59879..1355ff0 100755 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -123,7 +123,7 @@ export async function initHomePage(): Promise { displayEmojis(spAddress); } catch (error) { console.error('❌ Failed to get device address:', error); - if (error.message.includes('Wallet keys not available')) { + if ((error as Error).message.includes('Wallet keys not available')) { console.error('❌ Wallet keys not available - authentication failed'); throw new Error('Authentication failed - wallet keys not available'); } @@ -587,10 +587,11 @@ async function showSecurityModeSelector(): Promise { // Sélection d'un mode options.forEach(option => { + const htmlOption = option as HTMLElement; option.addEventListener('click', () => { - options.forEach(opt => opt.style.borderColor = '#e1e8ed'); - option.style.borderColor = '#27ae60'; - option.style.background = '#f8fff8'; + options.forEach(opt => (opt as HTMLElement).style.borderColor = '#e1e8ed'); + htmlOption.style.borderColor = '#27ae60'; + htmlOption.style.background = '#f8fff8'; selectedMode = option.getAttribute('data-mode'); confirmBtn.disabled = false; confirmBtn.style.opacity = '1'; @@ -598,13 +599,13 @@ async function showSecurityModeSelector(): Promise { // Effet hover option.addEventListener('mouseenter', () => { - if (option.style.borderColor !== '#27ae60') { - option.style.borderColor = '#3498db'; + if (htmlOption.style.borderColor !== '#27ae60') { + htmlOption.style.borderColor = '#3498db'; } }); option.addEventListener('mouseleave', () => { - if (option.style.borderColor !== '#27ae60') { - option.style.borderColor = '#e1e8ed'; + if (htmlOption.style.borderColor !== '#27ae60') { + htmlOption.style.borderColor = '#e1e8ed'; } }); }); diff --git a/src/pages/security-setup/security-setup.ts b/src/pages/security-setup/security-setup.ts index 993f315..77c7c1d 100644 --- a/src/pages/security-setup/security-setup.ts +++ b/src/pages/security-setup/security-setup.ts @@ -88,10 +88,9 @@ document.addEventListener('DOMContentLoaded', async () => { const pbkdf2Key = await secureCredentialsService.generatePBKDF2Key(selectedMode); console.log('✅ PBKDF2 key generated and stored securely'); - // Rediriger vers la page de génération du wallet - console.log('🔐 Security setup completed, checking storage state...'); - const { checkStorageStateAndNavigate } = await import('../../router'); - await checkStorageStateAndNavigate(); + // Rediriger directement vers la page de génération du wallet + console.log('🔐 Security setup completed, redirecting to wallet-setup...'); + window.location.href = '/src/pages/wallet-setup/wallet-setup.html'; } catch (error) { console.error('❌ PBKDF2 key generation failed:', error); diff --git a/src/pages/wallet-setup/wallet-setup.ts b/src/pages/wallet-setup/wallet-setup.ts index cc61f26..1940f4b 100644 --- a/src/pages/wallet-setup/wallet-setup.ts +++ b/src/pages/wallet-setup/wallet-setup.ts @@ -142,19 +142,27 @@ document.addEventListener('DOMContentLoaded', async () => { console.log('🔍 Testing all security modes to find a PBKDF2 key...'); // Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide + // Mettre 'none' en premier pour éviter d'ouvrir la fenêtre du navigateur const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> = - ['otp', 'password', 'none', 'os', 'proton-pass']; + ['none', 'otp', 'password', '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; + // Vérifier d'abord silencieusement si une clé existe + const hasKey = await secureCredentialsService.hasPBKDF2Key(mode); + if (hasKey) { + // Si une clé existe, essayer de la récupérer + const key = await secureCredentialsService.retrievePBKDF2Key(mode); + if (key) { + currentMode = mode; + console.log(`✅ PBKDF2 key found for security mode: ${mode}`); + break; + } + } else { + console.log(`⚠️ No PBKDF2 key found for mode ${mode}`); } } catch (error) { console.log(`⚠️ No PBKDF2 key found for mode ${mode}`); @@ -284,15 +292,13 @@ document.addEventListener('DOMContentLoaded', async () => { 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é + encrypted_wallet: encryptedWallet // Wallet chiffré }; 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é }); @@ -334,7 +340,6 @@ document.addEventListener('DOMContentLoaded', async () => { 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'); @@ -393,7 +398,6 @@ document.addEventListener('DOMContentLoaded', async () => { 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 }); @@ -449,7 +453,7 @@ document.addEventListener('DOMContentLoaded', async () => { } // Étape 4: Finalisation - updateStatus('✅ Wallet sauvegardé avec succès!', 'success'); + updateStatus('✅ Wallet sauvegardé avec succès! Redirection automatique dans 3 secondes...', 'success'); updateProgress(100); console.log('🎉 Wallet setup completed successfully - wallet saved with birthday_waiting state'); @@ -457,6 +461,13 @@ document.addEventListener('DOMContentLoaded', async () => { // Activer le bouton continuer continueBtn.disabled = false; + console.log('✅ Continue button enabled'); + + // Redirection automatique après 3 secondes si l'utilisateur ne clique pas + setTimeout(() => { + console.log('🔄 Auto-redirecting to birthday setup after timeout...'); + window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; + }, 3000); } catch (error) { console.error('❌ Error during wallet setup:', error); @@ -464,10 +475,10 @@ document.addEventListener('DOMContentLoaded', async () => { } // Gestion du bouton continuer - continueBtn.addEventListener('click', () => { - console.log('🔗 Redirecting to pairing page...'); - console.log('💰 Wallet setup completed, checking storage state...'); - const { checkStorageStateAndNavigate } = await import('../../router'); - await checkStorageStateAndNavigate(); + continueBtn.addEventListener('click', async () => { + console.log('🔗 Redirecting to birthday setup...'); + console.log('💰 Wallet setup completed, redirecting to birthday configuration...'); + // Rediriger directement vers la page de configuration de la date anniversaire + window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }); }); \ No newline at end of file diff --git a/src/router.ts b/src/router.ts index 06141bd..e02436d 100755 --- a/src/router.ts +++ b/src/router.ts @@ -67,7 +67,7 @@ export async function checkStorageStateAndNavigate(): Promise { const secureCredentialsService = SecureCredentialsService.getInstance(); const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> = - ['otp', 'password', 'none', 'os', 'proton-pass']; + ['none', 'otp', 'password', 'os', 'proton-pass']; let hasPBKDF2Key = false; for (const mode of allSecurityModes) { @@ -129,6 +129,13 @@ async function handleLocation(path: string) { console.log('📍 Current route set to:', currentRoute); console.log('📍 Route HTML:', routeHtml); + // Pour les pages de setup, rediriger directement vers la page HTML + if (path === 'security-setup' || path === 'wallet-setup' || path === 'birthday-setup') { + console.log('📍 Processing setup route:', path); + window.location.href = routeHtml; + return; + } + const content = document.getElementById('containerId'); console.log('📍 Container element found:', !!content); if (content) { @@ -157,12 +164,6 @@ async function handleLocation(path: string) { } catch (error) { console.error('❌ Failed to initialize home page:', error); } - } else if (path === 'security-setup' || path === 'wallet-setup' || path === 'birthday-setup') { - console.log('📍 Processing setup route:', path); - - // Pour les pages de setup, rediriger directement vers la page HTML - window.location.href = routeHtml; - return; } else { console.log('📍 Processing other route:', path); const html = await fetch(routeHtml).then(data => data.text()); diff --git a/src/services/credentials/storage.service.ts b/src/services/credentials/storage.service.ts index 4ed166c..d722711 100644 --- a/src/services/credentials/storage.service.ts +++ b/src/services/credentials/storage.service.ts @@ -1,5 +1,6 @@ /** - * StorageService - Gestion du stockage des credentials + * StorageService - Gestion du stockage des credentials de pairing + * Utilisé uniquement pour stocker les credentials après le processus de pairing */ import { secureLogger } from '../secure-logger'; import { CredentialData } from './types'; @@ -8,7 +9,7 @@ import { DATABASE_CONFIG } from '../database-config'; export class StorageService { private static instance: StorageService; private dbName = DATABASE_CONFIG.name; - private storeName = DATABASE_CONFIG.stores.credentials.name; // Store séparé pour les clés PBKDF2 + private storeName = DATABASE_CONFIG.stores.credentials.name; // Store pour les credentials de pairing private dbVersion = DATABASE_CONFIG.version; private constructor() {} @@ -20,178 +21,14 @@ export class StorageService { return StorageService.instance; } - /** - * Stocke une clé en clair (non recommandé) - */ - async storePlainKey(key: string): Promise { - try { - secureLogger.warn('Storing key in plain text (not recommended)', { - component: 'StorageService', - operation: 'storePlainKey' - }); - - const db = await this.openDatabase(); - const transaction = db.transaction([this.storeName], 'readwrite'); - const store = transaction.objectStore(this.storeName); - - await new Promise((resolve, reject) => { - const request = store.put(key, 'plain-pbkdf2-key'); - request.onsuccess = () => resolve(); - request.onerror = () => reject(request.error); - }); - - secureLogger.info('Plain key stored successfully', { - component: 'StorageService', - operation: 'storePlainKey' - }); - - } catch (error) { - secureLogger.error('Failed to store plain key', { - component: 'StorageService', - operation: 'storePlainKey', - error: error instanceof Error ? error.message : String(error) - }); - throw error; - } - } - - /** - * Récupère une clé en clair depuis IndexedDB - */ - async retrievePlainKey(): Promise { - try { - const db = await this.openDatabase(); - const transaction = db.transaction([this.storeName], 'readonly'); - const store = transaction.objectStore(this.storeName); - - const result = await new Promise((resolve, reject) => { - const request = store.get('plain-pbkdf2-key'); - request.onsuccess = () => resolve(request.result || null); - request.onerror = () => reject(request.error); - }); - - return result; - } catch (error) { - secureLogger.error('Failed to retrieve plain key', { - component: 'StorageService', - operation: 'retrievePlainKey', - error: error instanceof Error ? error.message : String(error) - }); - return null; - } - } - - /** - * Récupère une clé chiffrée depuis IndexedDB - */ - async retrieveEncryptedKey(): Promise { - try { - const db = await this.openDatabase(); - const transaction = db.transaction([this.storeName], 'readonly'); - const store = transaction.objectStore(this.storeName); - - const result = await new Promise((resolve, reject) => { - const request = store.get('encrypted-pbkdf2-key'); - request.onsuccess = () => resolve(request.result || null); - request.onerror = () => reject(request.error); - }); - - return result; - } catch (error) { - secureLogger.error('Failed to retrieve encrypted key', { - component: 'StorageService', - operation: 'retrieveEncryptedKey', - error: error instanceof Error ? error.message : String(error) - }); - return null; - } - } - - /** - * Stocke une clé chiffrée dans IndexedDB - */ - async storeEncryptedKey(encryptedKey: string, securityMode?: string): Promise { - try { - secureLogger.info('Storing encrypted key', { - component: 'StorageService', - operation: 'storeEncryptedKey' - }); - - const db = await this.openDatabase(); - const transaction = db.transaction([this.storeName], 'readwrite'); - const store = transaction.objectStore(this.storeName); - - await new Promise((resolve, reject) => { - const request = store.put(encryptedKey, 'encrypted-pbkdf2-key'); - request.onsuccess = () => resolve(); - request.onerror = () => reject(request.error); - }); - - secureLogger.info('Encrypted key stored successfully', { - component: 'StorageService', - operation: 'storeEncryptedKey' - }); - } catch (error) { - secureLogger.error('Failed to store encrypted key', { - component: 'StorageService', - operation: 'storeEncryptedKey', - error: error instanceof Error ? error.message : String(error) - }); - throw error; - } - } /** * Ouvre la base de données IndexedDB */ private async openDatabase(): Promise { - return new Promise((resolve, reject) => { - const request = indexedDB.open(this.dbName, this.dbVersion); - - request.onerror = () => { - const error = new Error('Database open failed'); - secureLogger.error('Failed to open IndexedDB', error, { - component: 'StorageService', - operation: 'openDatabase' - }); - reject(request.error); - }; - - request.onsuccess = () => { - secureLogger.info('IndexedDB opened successfully', { - component: 'StorageService', - operation: 'openDatabase' - }); - resolve(request.result); - }; - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result; - 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}`); - } - }); - }; - }); + // Utiliser la fonction centralisée openDatabase + const { openDatabase } = await import('../database-config'); + return openDatabase(); } /** diff --git a/src/services/credentials/webauthn.service.ts b/src/services/credentials/webauthn.service.ts index 3947556..b04c0f8 100644 --- a/src/services/credentials/webauthn.service.ts +++ b/src/services/credentials/webauthn.service.ts @@ -201,11 +201,11 @@ export class WebAuthnService { const response = credential.response as AuthenticatorAttestationResponse; const credentialId = Array.from(new Uint8Array(credential.rawId)).map(b => b.toString(16).padStart(2, '0')).join(''); - + // TEST: Store credentialId in sessionStorage for testing console.log('🔐 TEST: Storing credentialId in sessionStorage:', credentialId); sessionStorage.setItem('webauthn_credential_id', credentialId); - + return { id: credentialId, publicKey: Array.from(new Uint8Array(response.getPublicKey()!)) @@ -258,7 +258,7 @@ export class WebAuthnService { try { // TEST: Log credentialId used for encryption console.log('🔐 TEST: credentialId used for encryption:', credentialId); - + const db = await this.openDatabase(); console.log(`🔍 Available stores in ${DATABASE_CONFIG.name}:`, Array.from(db.objectStoreNames)); const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readwrite'); @@ -266,11 +266,8 @@ export class WebAuthnService { await new Promise((resolve, reject) => { // Utiliser le securityMode comme clé d'enregistrement - // NE PAS stocker credentialId avec la clé chiffrée - const request = store.put({ - encryptedKey, // Clé PBKDF2 chiffrée avec WebAuthn - timestamp: Date.now() - }, securityMode); + // Stocker directement la clé chiffrée pour cohérence avec les autres modes + const request = store.put(encryptedKey, securityMode); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); @@ -287,6 +284,28 @@ export class WebAuthnService { } + /** + * Vérifie silencieusement si une clé PBKDF2 est stockée pour un mode de sécurité + */ + async hasStoredKey(securityMode: SecurityMode): Promise { + try { + const db = await this.openDatabase(); + 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(securityMode); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + + return result && typeof result === 'string'; + } catch (error) { + console.log(`🔍 No PBKDF2 key found for security mode: ${securityMode}`); + return false; + } + } + /** * Récupère et déchiffre la clé PBKDF2 avec WebAuthn */ @@ -303,7 +322,7 @@ export class WebAuthnService { request.onerror = () => reject(request.error); }); - if (!result || !result.encryptedKey) { + if (!result || typeof result !== 'string') { console.log(`🔍 No PBKDF2 key found for security mode: ${securityMode}`); return null; } @@ -311,11 +330,11 @@ export class WebAuthnService { // Récupérer le credentialId dynamiquement via WebAuthn // IMPORTANT: Cela nécessite une interaction utilisateur (authentification biométrique) console.log('🔐 Requesting WebAuthn authentication to retrieve credential...'); - + // TEST: Try to get credentialId from sessionStorage first const storedCredentialId = sessionStorage.getItem('webauthn_credential_id'); let credentialId: string | null = null; - + if (storedCredentialId) { console.log('🔐 Using credentialId from sessionStorage:', storedCredentialId); credentialId = storedCredentialId; @@ -327,7 +346,7 @@ export class WebAuthnService { sessionStorage.setItem('webauthn_credential_id', credentialId); } } - + if (!credentialId) { console.log('🔍 WebAuthn authentication required but not available'); console.log('ℹ️ For proton-pass or os mode, user interaction is required to decrypt the key'); @@ -336,7 +355,7 @@ export class WebAuthnService { // Déchiffrer la clé avec le credentialId WebAuthn console.log('🔐 Decrypting PBKDF2 key with credentialId:', credentialId); - const encrypted = atob(result.encryptedKey); + const encrypted = atob(result); const combined = new Uint8Array(encrypted.length); for (let i = 0; i < encrypted.length; i++) { combined[i] = encrypted.charCodeAt(i); @@ -346,7 +365,7 @@ export class WebAuthnService { const salt = combined.slice(0, 16); const iv = combined.slice(16, 28); const encryptedData = combined.slice(28); - + console.log('🔐 Extraction complete:', { saltLength: salt.length, ivLength: iv.length, diff --git a/src/services/database.service.ts b/src/services/database.service.ts index 67e555b..e27b523 100755 --- a/src/services/database.service.ts +++ b/src/services/database.service.ts @@ -510,14 +510,31 @@ export class Database { } public async getObject(storeName: string, key: string): Promise { - const db = await this.getDb(); + console.log(`🔍 DEBUG: Database.getObject - storeName: ${storeName}, key: ${key}`); + + // Utiliser directement IndexedDB au lieu du service worker pour éviter les problèmes de synchronisation + const db = await new Promise((resolve, reject) => { + const request = indexedDB.open(this.dbName, this.dbVersion); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + + console.log(`🔍 DEBUG: Database.getObject - db obtained directly, objectStoreNames:`, Array.from(db.objectStoreNames)); const tx = db.transaction(storeName, 'readonly'); const store = tx.objectStore(storeName); + console.log(`🔍 DEBUG: Database.getObject - store opened: ${store.name}`); const result = await new Promise((resolve, reject) => { const getRequest = store.get(key); - getRequest.onsuccess = () => resolve(getRequest.result); - getRequest.onerror = () => reject(getRequest.error); + getRequest.onsuccess = () => { + console.log(`🔍 DEBUG: Database.getObject - getRequest success, result:`, getRequest.result); + resolve(getRequest.result); + }; + getRequest.onerror = () => { + console.log(`🔍 DEBUG: Database.getObject - getRequest error:`, getRequest.error); + reject(getRequest.error); + }; }); + console.log(`🔍 DEBUG: Database.getObject - final result:`, result); return result ?? null; // Convert undefined to null } diff --git a/src/services/secure-credentials.service.ts b/src/services/secure-credentials.service.ts index 14d9d79..1b0ab74 100644 --- a/src/services/secure-credentials.service.ts +++ b/src/services/secure-credentials.service.ts @@ -94,34 +94,138 @@ export class SecureCredentialsService { } } + /** + * Vérifie silencieusement si une clé PBKDF2 existe pour un mode de sécurité + * sans déclencher d'interactions utilisateur (comme les fenêtres du navigateur) + */ + async hasPBKDF2Key(securityMode: SecurityMode): Promise { + try { + switch (securityMode) { + case 'proton-pass': + case 'os': + // Pour WebAuthn, vérifier si une clé chiffrée existe + const { WebAuthnService } = await import('./credentials/webauthn.service'); + const webAuthnService = WebAuthnService.getInstance(); + return await webAuthnService.hasStoredKey(securityMode); + + case 'otp': + // Vérifier si une clé en clair existe dans pbkdf2keys + const plainKey = await this.getPBKDF2KeyFromStore(securityMode); + return plainKey !== null; + + case 'password': + // Vérifier silencieusement si une clé chiffrée existe dans pbkdf2keys + // sans déclencher l'API Credential Management + const encryptedPasswordData = await this.getPBKDF2KeyFromStore(securityMode); + return encryptedPasswordData !== null; + + case 'none': + // Vérifier si une clé chiffrée avec la clé en dur existe dans pbkdf2keys + const encryptedData = await this.getPBKDF2KeyFromStore(securityMode); + return encryptedData !== null; + + default: + return false; + } + } catch (error) { + secureLogger.error('Failed to check PBKDF2 key existence', { + component: 'SecureCredentialsService', + operation: 'hasPBKDF2Key', + error: error instanceof Error ? error.message : String(error) + }); + return false; + } + } + + /** + * Récupère une clé PBKDF2 chiffrée depuis le store pbkdf2keys + */ + private async getPBKDF2KeyFromStore(securityMode: SecurityMode): Promise { + try { + const { DATABASE_CONFIG, openDatabase } = await import('./database-config'); + const db = await openDatabase(); + 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(securityMode); + request.onsuccess = () => resolve(request.result || null); + request.onerror = () => reject(request.error); + }); + + return result; + } catch (error) { + secureLogger.error('Failed to retrieve PBKDF2 key from pbkdf2keys store', { + component: 'SecureCredentialsService', + operation: 'getPBKDF2KeyFromStore', + error: error instanceof Error ? error.message : String(error) + }); + return null; + } + } + + /** + * Stocke une clé PBKDF2 chiffrée dans le store pbkdf2keys + */ + private async storePBKDF2KeyInStore(encryptedKey: string, securityMode: SecurityMode): Promise { + try { + const { DATABASE_CONFIG, openDatabase } = await import('./database-config'); + const db = await openDatabase(); + const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readwrite'); + const store = transaction.objectStore(DATABASE_CONFIG.stores.pbkdf2keys.name); + + await new Promise((resolve, reject) => { + const request = store.put(encryptedKey, securityMode); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); + + secureLogger.info('PBKDF2 key stored in pbkdf2keys store', { + component: 'SecureCredentialsService', + operation: 'storePBKDF2KeyInStore', + securityMode + }); + } catch (error) { + secureLogger.error('Failed to store PBKDF2 key in pbkdf2keys store', { + component: 'SecureCredentialsService', + operation: 'storePBKDF2KeyInStore', + error: error instanceof Error ? error.message : String(error) + }); + throw error; + } + } + /** * Récupère une clé PBKDF2 existante selon le mode de sécurité */ async retrievePBKDF2Key(securityMode: SecurityMode): Promise { try { - const { StorageService } = await import('./credentials/storage.service'); + const { DATABASE_CONFIG, openDatabase } = await import('./database-config'); const { WebAuthnService } = await import('./credentials/webauthn.service'); - const storageService = StorageService.getInstance(); const webAuthnService = WebAuthnService.getInstance(); switch (securityMode) { case 'proton-pass': case 'os': - // Récupérer la clé chiffrée avec WebAuthn + // Récupérer la clé chiffrée avec WebAuthn depuis pbkdf2keys return await webAuthnService.retrieveKeyWithWebAuthn(securityMode); case 'otp': - // Récupérer la clé en clair (l'OTP protège l'accès) - return await storageService.retrievePlainKey(); + // Récupérer la clé en clair depuis pbkdf2keys + return await this.getPBKDF2KeyFromStore(securityMode); case 'password': - // Récupérer la clé chiffrée avec mot de passe - return await storageService.retrieveEncryptedKey(); + // Récupérer la clé chiffrée avec mot de passe depuis pbkdf2keys + const encryptedPasswordData = await this.getPBKDF2KeyFromStore(securityMode); + if (encryptedPasswordData) { + return await this.decryptPBKDF2KeyWithPassword(encryptedPasswordData); + } + return null; case 'none': - // Récupérer la clé chiffrée avec la clé en dur - const encryptedData = await storageService.retrieveEncryptedKey(); + // Récupérer la clé chiffrée avec la clé en dur depuis pbkdf2keys + const encryptedData = await this.getPBKDF2KeyFromStore(securityMode); if (encryptedData) { const { EncryptionService } = await import('./encryption.service'); const encryptionService = EncryptionService.getInstance(); @@ -157,11 +261,9 @@ export class SecureCredentialsService { // Import dynamique des services const { EncryptionService } = await import('./encryption.service'); const { WebAuthnService } = await import('./credentials/webauthn.service'); - const { StorageService } = await import('./credentials/storage.service'); const encryptionService = EncryptionService.getInstance(); const webAuthnService = WebAuthnService.getInstance(); - const storageService = StorageService.getInstance(); // Essayer d'abord de récupérer une clé existante const existingKey = await this.retrievePBKDF2Key(securityMode); @@ -188,18 +290,18 @@ export class SecureCredentialsService { console.log('🔐 Setting up OTP authentication for PBKDF2 key...'); const otpSecret = await this.generateOTPSecret(); console.log('🔐 OTP Secret generated:', otpSecret); - // Stocker la clé PBKDF2 en clair (l'OTP protège l'accès, pas le stockage) - await storageService.storePlainKey(pbkdf2Key); + // Stocker la clé PBKDF2 en clair dans pbkdf2keys (l'OTP protège l'accès, pas le stockage) + await this.storePBKDF2KeyInStore(pbkdf2Key, securityMode); // Afficher le QR code pour l'utilisateur this.displayOTPQRCode(otpSecret); break; case 'password': - // Demander un mot de passe à l'utilisateur et chiffrer la clé - console.log('🔐 Storing PBKDF2 key with password encryption...'); - const userPassword = await this.promptForPassword(); + // Utiliser l'API Credential Management du navigateur + console.log('🔐 Storing PBKDF2 key with browser password manager...'); + const userPassword = await this.promptForPasswordWithBrowser(); const encryptedKey = await encryptionService.encrypt(pbkdf2Key, userPassword); - await storageService.storeEncryptedKey(encryptedKey, securityMode); + await this.storePBKDF2KeyInStore(encryptedKey, securityMode); break; case 'none': @@ -207,7 +309,7 @@ export class SecureCredentialsService { console.log('⚠️ Storing PBKDF2 key with hardcoded encryption (not recommended)...'); const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE'; const encryptedKeyNone = await encryptionService.encrypt(pbkdf2Key, hardcodedKey); - await storageService.storeEncryptedKey(encryptedKeyNone, securityMode); + await this.storePBKDF2KeyInStore(encryptedKeyNone, securityMode); break; default: @@ -736,15 +838,302 @@ QR Code URL: ${qrUrl}`); */ private async promptForPassword(): Promise { return new Promise((resolve, reject) => { - const password = prompt('Entrez un mot de passe pour chiffrer la clé PBKDF2:'); - if (password) { - resolve(password); - } else { + // Créer une interface utilisateur pour saisir le mot de passe + const modal = document.createElement('div'); + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + `; + + const dialog = document.createElement('div'); + dialog.style.cssText = ` + background: white; + padding: 30px; + border-radius: 12px; + box-shadow: 0 20px 40px rgba(0,0,0,0.3); + max-width: 400px; + width: 90%; + `; + + dialog.innerHTML = ` +

🔐 Mot de passe de sécurité

+

+ Entrez un mot de passe fort pour chiffrer votre clé PBKDF2.
+ Attention : Ce mot de passe ne sera pas sauvegardé et ne pourra pas être récupéré ! +

+ +
+ + +
+ `; + + modal.appendChild(dialog); + document.body.appendChild(modal); + + const passwordInput = dialog.querySelector('#passwordInput') as HTMLInputElement; + const cancelBtn = dialog.querySelector('#cancelBtn') as HTMLButtonElement; + const confirmBtn = dialog.querySelector('#confirmBtn') as HTMLButtonElement; + + // Focus sur l'input + passwordInput.focus(); + + // Gestion des événements + let cleanup = () => { + document.body.removeChild(modal); + }; + + cancelBtn.addEventListener('click', () => { + cleanup(); reject(new Error('Password prompt cancelled')); - } + }); + + confirmBtn.addEventListener('click', () => { + const password = passwordInput.value.trim(); + if (password.length < 8) { + alert('Le mot de passe doit contenir au moins 8 caractères'); + passwordInput.focus(); + return; + } + cleanup(); + resolve(password); + }); + + // Gestion de la touche Entrée + passwordInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + confirmBtn.click(); + } + }); + + // Gestion de la touche Échap + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + cleanup(); + reject(new Error('Password prompt cancelled')); + } + }; + document.addEventListener('keydown', handleEscape); + + // Nettoyer l'event listener quand le modal est fermé + const originalCleanup = cleanup; + cleanup = () => { + document.removeEventListener('keydown', handleEscape); + originalCleanup(); + }; }); } + /** + * Demande un mot de passe à l'utilisateur en utilisant l'API Credential Management du navigateur + */ + private async promptForPasswordWithBrowser(): Promise { + // Vérifier si l'API Credential Management est disponible + if (!navigator.credentials) { + console.warn('⚠️ Credential Management API not available, falling back to modal'); + return this.promptForPassword(); + } + + try { + // Essayer de récupérer un mot de passe existant + const existingCredential = await navigator.credentials.get({ + password: true, + mediation: 'optional' + }); + + if (existingCredential && existingCredential.type === 'password') { + const passwordCredential = existingCredential as PasswordCredential; + console.log('🔐 Retrieved existing password from browser'); + return passwordCredential.password; + } + } catch (error) { + console.log('🔐 No existing password found, will create new one'); + } + + // Si aucun mot de passe existant, créer un nouveau + return new Promise((resolve, reject) => { + const modal = document.createElement('div'); + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + `; + + const dialog = document.createElement('div'); + dialog.style.cssText = ` + background: white; + padding: 30px; + border-radius: 12px; + box-shadow: 0 20px 40px rgba(0,0,0,0.3); + max-width: 400px; + width: 90%; + `; + + dialog.innerHTML = ` +

🔐 Mot de passe de sécurité

+

+ Entrez un mot de passe fort pour chiffrer votre clé PBKDF2.
+ Le navigateur vous proposera de sauvegarder ce mot de passe. +

+ +
+ + +
+ `; + + modal.appendChild(dialog); + document.body.appendChild(modal); + + const passwordInput = dialog.querySelector('#passwordInput') as HTMLInputElement; + const cancelBtn = dialog.querySelector('#cancelBtn') as HTMLButtonElement; + const confirmBtn = dialog.querySelector('#confirmBtn') as HTMLButtonElement; + + passwordInput.focus(); + + let cleanup = () => { + document.body.removeChild(modal); + }; + + cancelBtn.addEventListener('click', () => { + cleanup(); + reject(new Error('Password prompt cancelled')); + }); + + confirmBtn.addEventListener('click', async () => { + const password = passwordInput.value.trim(); + if (password.length < 8) { + alert('Le mot de passe doit contenir au moins 8 caractères'); + passwordInput.focus(); + return; + } + + try { + // Sauvegarder le mot de passe dans le gestionnaire de mots de passe du navigateur + if (navigator.credentials && navigator.credentials.create) { + const credential = new PasswordCredential({ + id: '4nk-pbkdf2-password', + password: password, + name: '4NK PBKDF2 Password', + iconURL: '/favicon.ico' + }); + + await navigator.credentials.store(credential); + console.log('🔐 Password saved to browser password manager'); + } + } catch (error) { + console.warn('⚠️ Failed to save password to browser:', error); + // Continuer même si la sauvegarde échoue + } + + cleanup(); + resolve(password); + }); + + // Gestion de la touche Entrée + passwordInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + confirmBtn.click(); + } + }); + + // Gestion de la touche Échap + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + cleanup(); + reject(new Error('Password prompt cancelled')); + } + }; + document.addEventListener('keydown', handleEscape); + + // Nettoyer l'event listener quand le modal est fermé + const originalCleanup = cleanup; + cleanup = () => { + document.removeEventListener('keydown', handleEscape); + originalCleanup(); + }; + }); + } + + /** + * Déchiffre une clé PBKDF2 avec le mot de passe du navigateur + */ + async decryptPBKDF2KeyWithPassword(encryptedKey: string): Promise { + try { + // Récupérer le mot de passe depuis le gestionnaire de mots de passe du navigateur + const password = await this.getPasswordFromBrowser(); + if (!password) { + console.warn('⚠️ No password found in browser, falling back to manual input'); + return null; + } + + // Déchiffrer avec le mot de passe + const { EncryptionService } = await import('./encryption.service'); + const encryptionService = EncryptionService.getInstance(); + return await encryptionService.decrypt(encryptedKey, password); + } catch (error) { + secureLogger.error('Failed to decrypt PBKDF2 key with password', { + component: 'SecureCredentialsService', + operation: 'decryptPBKDF2KeyWithPassword', + error: error instanceof Error ? error.message : String(error) + }); + return null; + } + } + + /** + * Récupère le mot de passe depuis le gestionnaire de mots de passe du navigateur + */ + private async getPasswordFromBrowser(): Promise { + // Vérifier si l'API Credential Management est disponible + if (!navigator.credentials) { + console.warn('⚠️ Credential Management API not available'); + return null; + } + + try { + const credential = await navigator.credentials.get({ + password: true, + mediation: 'optional' + }); + + if (credential && credential.type === 'password') { + const passwordCredential = credential as PasswordCredential; + console.log('🔐 Retrieved password from browser password manager'); + return passwordCredential.password; + } + } catch (error) { + console.log('🔐 No password found in browser password manager'); + } + + return null; + } + /** * Valide la force d'un mot de passe */ diff --git a/src/services/service.ts b/src/services/service.ts index 030bb19..f6572bc 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -867,7 +867,7 @@ export default class Services { const newAmount = this.getAmount(); console.log(`💰 Amount after transaction scan: ${newAmount}`); - if (newAmount > 0) { + if (newAmount > BigInt(0)) { this.updateUserStatus(`💰 Found ${newAmount} tokens in wallet!`); } else { this.updateUserStatus('⏳ Transaction processed, waiting for confirmation...'); @@ -1293,7 +1293,7 @@ export default class Services { // Update last_scan to current block height device.sp_wallet.last_scan = this.currentBlockHeight; - await this.updateDeviceInDatabase(device); + await this.saveDeviceInDatabase(device); console.log('✅ Wallet last_scan updated to current block height'); } else { console.log('🔄 Using safe scan blocks...'); @@ -1318,7 +1318,7 @@ export default class Services { console.log(`💰 Amount after block scan: ${updatedAmount}`); // Update user with scan results - if (updatedAmount > 0) { + if (updatedAmount > BigInt(0)) { this.updateUserStatus(`💰 Wallet updated! Found ${updatedAmount} tokens`); } else { this.updateUserStatus('⏳ Transaction processed, waiting for confirmation...'); @@ -1829,11 +1829,30 @@ export default class Services { } async getDeviceFromDatabase(): Promise { - const db = await Database.getInstance(); + console.log('🔍 DEBUG: getDeviceFromDatabase - attempting to get wallet with key "1"'); + + // Utiliser directement IndexedDB au lieu du service Database pour éviter les problèmes de service worker + const db = await new Promise((resolve, reject) => { + const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + const walletStore = DATABASE_CONFIG.stores.wallet.name; + console.log('🔍 DEBUG: getDeviceFromDatabase - db opened directly, objectStoreNames:', Array.from(db.objectStoreNames)); + try { - const dbRes = await db.getObject(walletStore, '1'); + const dbRes = await new Promise((resolve, reject) => { + const tx = db.transaction(walletStore, 'readonly'); + const store = tx.objectStore(walletStore); + const getRequest = store.get('1'); + getRequest.onsuccess = () => resolve(getRequest.result); + getRequest.onerror = () => reject(getRequest.error); + }); + + console.log('🔍 DEBUG: getDeviceFromDatabase - db.getObject result:', dbRes); if (!dbRes) { + console.log('🔍 DEBUG: getDeviceFromDatabase - no data found for key "1"'); return null; } @@ -1847,17 +1866,23 @@ export default class Services { const secureCredentialsService = SecureCredentialsService.getInstance(); // Get all security modes to find which one works - const allSecurityModes = ['otp', 'password', 'none', 'os', 'proton-pass']; + // Mettre 'none' en premier pour éviter d'ouvrir la fenêtre du navigateur + const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass']; let pbkdf2Key: string | null = null; let workingMode: string | null = null; for (const mode of allSecurityModes) { try { - const key = await secureCredentialsService.retrievePBKDF2Key(mode as any); - if (key) { - pbkdf2Key = key; - workingMode = mode; - break; + // Vérifier d'abord silencieusement si une clé existe + const hasKey = await secureCredentialsService.hasPBKDF2Key(mode as any); + if (hasKey) { + // Si une clé existe, essayer de la récupérer + const key = await secureCredentialsService.retrievePBKDF2Key(mode as any); + if (key) { + pbkdf2Key = key; + workingMode = mode; + break; + } } } catch (e) { // Continue to next mode @@ -2013,7 +2038,7 @@ export default class Services { has_spend_key: !!device.sp_wallet?.spend_key, has_scan_key: !!device.sp_wallet?.scan_key, birthday: device.sp_wallet?.birthday, - sp_address: device.sp_address + sp_address: device.sp_wallet?.address }); await this.saveDeviceInDatabase(device); diff --git a/test-birthday-setup.html b/test-birthday-setup.html new file mode 100644 index 0000000..cecbbea --- /dev/null +++ b/test-birthday-setup.html @@ -0,0 +1,144 @@ + + + + + + Test Birthday Setup + + + +

Test Birthday Setup - Debug Device Not Found

+ +
+ + + + +
+ +
+ + + +