Compare commits
No commits in common. "057102300a0687a3cd61bc36099103e599d2af9b" and "d010dac706e6051dce989daff9c92f4df8016dc4" have entirely different histories.
057102300a
...
d010dac706
@ -199,6 +199,8 @@ export class SecureCredentialsComponent {
|
|||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const password = input.value;
|
const password = input.value;
|
||||||
|
|
||||||
|
const secureCredentialsService = SecureCredentialsService.getInstance();
|
||||||
|
const validation = secureCredentialsService.validatePasswordStrength(password);
|
||||||
const strengthDiv = document.getElementById('password-strength');
|
const strengthDiv = document.getElementById('password-strength');
|
||||||
|
|
||||||
if (strengthDiv) {
|
if (strengthDiv) {
|
||||||
@ -209,13 +211,10 @@ export class SecureCredentialsComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple password strength check
|
if (validation.score < 3) {
|
||||||
const score = password.length >= 12 ? (password.length >= 16 ? 5 : 4) : (password.length >= 8 ? 3 : 2);
|
|
||||||
|
|
||||||
if (score < 3) {
|
|
||||||
strengthDiv.className += ' weak';
|
strengthDiv.className += ' weak';
|
||||||
strengthDiv.textContent = 'Mot de passe faible';
|
strengthDiv.textContent = 'Mot de passe faible';
|
||||||
} else if (score < 5) {
|
} else if (validation.score < 5) {
|
||||||
strengthDiv.className += ' medium';
|
strengthDiv.className += ' medium';
|
||||||
strengthDiv.textContent = 'Mot de passe moyen';
|
strengthDiv.textContent = 'Mot de passe moyen';
|
||||||
} else {
|
} else {
|
||||||
@ -246,9 +245,9 @@ export class SecureCredentialsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: Implement credentials deletion
|
const secureCredentialsService = SecureCredentialsService.getInstance();
|
||||||
console.log('Credentials deletion requested but not implemented');
|
await secureCredentialsService.deleteCredentials();
|
||||||
this.showMessage('Suppression des credentials non implémentée', 'warning');
|
this.showMessage('Credentials supprimés avec succès', 'success');
|
||||||
await this.updateUI();
|
await this.updateUI();
|
||||||
|
|
||||||
// Émettre l'événement
|
// Émettre l'événement
|
||||||
|
|||||||
@ -39,6 +39,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigateur -->
|
||||||
|
<div class="security-option" data-mode="browser">
|
||||||
|
<div class="option-header">
|
||||||
|
<div class="option-icon">🌐</div>
|
||||||
|
<div class="option-title">Navigateur</div>
|
||||||
|
<div class="security-level medium">Moyennement sécurisé</div>
|
||||||
|
</div>
|
||||||
|
<div class="option-description">
|
||||||
|
Utilise les fonctionnalités de sécurité du navigateur
|
||||||
|
</div>
|
||||||
|
<div class="option-features">
|
||||||
|
<span class="feature">✅ WebAuthn standard</span>
|
||||||
|
<span class="feature">⚠️ Dépendant du navigateur</span>
|
||||||
|
<span class="feature">⚠️ Moins sécurisé que les options OS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Application 2FA -->
|
<!-- Application 2FA -->
|
||||||
<div class="security-option" data-mode="2fa">
|
<div class="security-option" data-mode="2fa">
|
||||||
<div class="option-header">
|
<div class="option-header">
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Permet à l'utilisateur de choisir comment sécuriser ses clés privées
|
* Permet à l'utilisateur de choisir comment sécuriser ses clés privées
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type SecurityMode = 'proton-pass' | 'os' | '2fa' | 'none';
|
export type SecurityMode = 'proton-pass' | 'os' | 'browser' | '2fa' | 'none';
|
||||||
|
|
||||||
export interface SecurityModeConfig {
|
export interface SecurityModeConfig {
|
||||||
mode: SecurityMode;
|
mode: SecurityMode;
|
||||||
@ -151,6 +151,14 @@ export class SecurityModeSelector {
|
|||||||
requiresConfirmation: false,
|
requiresConfirmation: false,
|
||||||
warnings: []
|
warnings: []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
mode: 'browser',
|
||||||
|
name: 'Navigateur',
|
||||||
|
description: 'Utilise les fonctionnalités de sécurité du navigateur',
|
||||||
|
securityLevel: 'medium',
|
||||||
|
requiresConfirmation: false,
|
||||||
|
warnings: []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
mode: '2fa',
|
mode: '2fa',
|
||||||
name: 'Application 2FA',
|
name: 'Application 2FA',
|
||||||
@ -211,6 +219,11 @@ export class SecurityModeSelector {
|
|||||||
'✅ Chiffrement matériel',
|
'✅ Chiffrement matériel',
|
||||||
'✅ Protection par mot de passe'
|
'✅ Protection par mot de passe'
|
||||||
],
|
],
|
||||||
|
'browser': [
|
||||||
|
'✅ WebAuthn standard',
|
||||||
|
'⚠️ Dépendant du navigateur',
|
||||||
|
'⚠️ Moins sécurisé que les options OS'
|
||||||
|
],
|
||||||
'2fa': [],
|
'2fa': [],
|
||||||
'none': []
|
'none': []
|
||||||
};
|
};
|
||||||
|
|||||||
@ -106,9 +106,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
continueBtn.addEventListener('click', () => {
|
continueBtn.addEventListener('click', () => {
|
||||||
console.log('🏠 Redirecting to main application...');
|
console.log('🏠 Redirecting to main application...');
|
||||||
// Rediriger vers l'application principale
|
// Rediriger vers l'application principale
|
||||||
console.log('🎂 Birthday setup completed, checking storage state...');
|
window.location.href = '/';
|
||||||
const { checkStorageStateAndNavigate } = await import('../../router');
|
|
||||||
await checkStorageStateAndNavigate();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
|
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
|
||||||
|
|||||||
@ -537,13 +537,22 @@ async function showSecurityModeSelector(): Promise<void> {
|
|||||||
<div style="color: #5a6c7d; font-size: 14px;">Utilise l'authentificateur intégré de votre système</div>
|
<div style="color: #5a6c7d; font-size: 14px;">Utilise l'authentificateur intégré de votre système</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="security-option" data-mode="otp" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
|
<div class="security-option" data-mode="browser" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
|
||||||
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
||||||
<span style="font-size: 20px; margin-right: 10px;">🔐</span>
|
<span style="font-size: 20px; margin-right: 10px;">🌐</span>
|
||||||
<span style="font-weight: 600; color: #2c3e50;">OTP (code à usage unique)</span>
|
<span style="font-weight: 600; color: #2c3e50;">Navigateur</span>
|
||||||
<span style="background: #d4edda; color: #155724; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: auto;">Sécurisé</span>
|
<span style="background: #fff3cd; color: #856404; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: auto;">Moyennement sécurisé</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="color: #5a6c7d; font-size: 14px;">Code OTP généré par Proton Pass, Google Authenticator, etc.</div>
|
<div style="color: #5a6c7d; font-size: 14px;">Utilise les fonctionnalités de sécurité du navigateur</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="security-option" data-mode="2fa" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
|
||||||
|
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
||||||
|
<span style="font-size: 20px; margin-right: 10px;">📱</span>
|
||||||
|
<span style="font-weight: 600; color: #2c3e50;">Application 2FA</span>
|
||||||
|
<span style="background: #f8d7da; color: #721c24; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: auto;">⚠️ Non sécurisé</span>
|
||||||
|
</div>
|
||||||
|
<div style="color: #5a6c7d; font-size: 14px;">Stockage en clair avec authentification 2FA</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="security-option" data-mode="password" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
|
<div class="security-option" data-mode="password" style="border: 2px solid #e1e8ed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.3s ease;">
|
||||||
@ -789,8 +798,7 @@ async function waitForCredentialsAvailability(): Promise<void> {
|
|||||||
isWaitingForCredentials = true;
|
isWaitingForCredentials = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
const secureCredentialsService = SecureCredentialsService.getInstance();
|
|
||||||
|
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 20;
|
const maxAttempts = 20;
|
||||||
@ -883,8 +891,7 @@ async function handleMainPairing(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Import and trigger authentication with selected mode
|
// Import and trigger authentication with selected mode
|
||||||
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
const secureCredentialsService = SecureCredentialsService.getInstance();
|
|
||||||
|
|
||||||
// Check if we have existing credentials (regardless of wallet existence)
|
// Check if we have existing credentials (regardless of wallet existence)
|
||||||
console.log('🔍 Checking for existing credentials...');
|
console.log('🔍 Checking for existing credentials...');
|
||||||
@ -1107,8 +1114,7 @@ async function handleDeleteAccount(): Promise<void> {
|
|||||||
|
|
||||||
// Get services
|
// Get services
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
const secureCredentialsService = SecureCredentialsService.getInstance();
|
|
||||||
|
|
||||||
// Delete all credentials
|
// Delete all credentials
|
||||||
await secureCredentialsService.deleteCredentials();
|
await secureCredentialsService.deleteCredentials();
|
||||||
|
|||||||
@ -122,6 +122,11 @@
|
|||||||
<div class="option-description">Utilise l'authentification biométrique de votre système</div>
|
<div class="option-description">Utilise l'authentification biométrique de votre système</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="security-option" data-mode="browser">
|
||||||
|
<div class="option-title">🌐 Navigateur</div>
|
||||||
|
<div class="option-description">Sauvegarde dans le gestionnaire de mots de passe du navigateur</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="security-option" data-mode="otp">
|
<div class="security-option" data-mode="otp">
|
||||||
<div class="option-title">🔐 OTP (code à usage unique)</div>
|
<div class="option-title">🔐 OTP (code à usage unique)</div>
|
||||||
<div class="option-description">Code OTP généré par Proton Pass, Google Authenticator, etc.</div>
|
<div class="option-description">Code OTP généré par Proton Pass, Google Authenticator, etc.</div>
|
||||||
|
|||||||
@ -89,9 +89,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
console.log('✅ PBKDF2 key generated and stored securely');
|
console.log('✅ PBKDF2 key generated and stored securely');
|
||||||
|
|
||||||
// Rediriger vers la page de génération du wallet
|
// Rediriger vers la page de génération du wallet
|
||||||
console.log('🔐 Security setup completed, checking storage state...');
|
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||||
const { checkStorageStateAndNavigate } = await import('../../router');
|
|
||||||
await checkStorageStateAndNavigate();
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ PBKDF2 key generation failed:', error);
|
console.error('❌ PBKDF2 key generation failed:', error);
|
||||||
|
|||||||
@ -112,7 +112,7 @@
|
|||||||
<div class="progress-bar" id="progressBar"></div>
|
<div class="progress-bar" id="progressBar"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="continue-btn" id="continueBtn" disabled>Ajouter la date anniversaire du wallet</button>
|
<button class="continue-btn" id="continueBtn" disabled>Continuer vers le Pairing</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="./wallet-setup.ts"></script>
|
<script type="module" src="./wallet-setup.ts"></script>
|
||||||
|
|||||||
@ -142,8 +142,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
console.log('🔍 Testing all security modes to find a PBKDF2 key...');
|
console.log('🔍 Testing all security modes to find a PBKDF2 key...');
|
||||||
|
|
||||||
// Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide
|
// Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide
|
||||||
const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
|
const allSecurityModes: Array<'browser' | 'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
|
||||||
['otp', 'password', 'none', 'os', 'proton-pass'];
|
['browser', 'otp', 'password', 'none', 'os', 'proton-pass'];
|
||||||
|
|
||||||
let currentMode: string | null = null;
|
let currentMode: string | null = null;
|
||||||
|
|
||||||
@ -466,8 +466,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// Gestion du bouton continuer
|
// Gestion du bouton continuer
|
||||||
continueBtn.addEventListener('click', () => {
|
continueBtn.addEventListener('click', () => {
|
||||||
console.log('🔗 Redirecting to pairing page...');
|
console.log('🔗 Redirecting to pairing page...');
|
||||||
console.log('💰 Wallet setup completed, checking storage state...');
|
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
|
||||||
const { checkStorageStateAndNavigate } = await import('../../router');
|
|
||||||
await checkStorageStateAndNavigate();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
191
src/router.ts
191
src/router.ts
@ -17,90 +17,10 @@ const routes: { [key: string]: string } = {
|
|||||||
account: '/src/pages/account/account.html',
|
account: '/src/pages/account/account.html',
|
||||||
chat: '/src/pages/chat/chat.html',
|
chat: '/src/pages/chat/chat.html',
|
||||||
signature: '/src/pages/signature/signature.html',
|
signature: '/src/pages/signature/signature.html',
|
||||||
'security-setup': '/src/pages/security-setup/security-setup.html',
|
|
||||||
'wallet-setup': '/src/pages/wallet-setup/wallet-setup.html',
|
|
||||||
'birthday-setup': '/src/pages/birthday-setup/birthday-setup.html',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export let currentRoute = '';
|
export let currentRoute = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie l'état du storage et détermine quelle page afficher selon l'état actuel
|
|
||||||
* Logique de progression :
|
|
||||||
* - Si pairing → account
|
|
||||||
* - Si date anniversaire → pairing
|
|
||||||
* - Si wallet → birthday-setup
|
|
||||||
* - Si pbkdf2 → wallet-setup
|
|
||||||
* - Sinon → security-setup
|
|
||||||
*/
|
|
||||||
export async function checkStorageStateAndNavigate(): Promise<void> {
|
|
||||||
console.log('🔍 Checking storage state to determine next step...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Vérifier l'état du pairing
|
|
||||||
const services = await Services.getInstance();
|
|
||||||
const isPaired = services.isPaired();
|
|
||||||
|
|
||||||
if (isPaired) {
|
|
||||||
console.log('✅ Device is paired, navigating to account page');
|
|
||||||
await navigate('account');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier si la date anniversaire est configurée (wallet avec birthday > 0)
|
|
||||||
const device = await services.getDeviceFromDatabase();
|
|
||||||
if (device && device.sp_wallet && device.sp_wallet.birthday > 0) {
|
|
||||||
console.log('🎂 Birthday is configured, navigating to pairing');
|
|
||||||
await navigate('home'); // Pour l'instant, rediriger vers home pour le pairing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier si le wallet existe (même avec birthday = 0)
|
|
||||||
if (device && device.sp_wallet) {
|
|
||||||
console.log('💰 Wallet exists but birthday not set, navigating to birthday-setup');
|
|
||||||
await navigate('birthday-setup');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier si une clé PBKDF2 existe
|
|
||||||
const { SecureCredentialsService } = await import('./services/secure-credentials.service');
|
|
||||||
const secureCredentialsService = SecureCredentialsService.getInstance();
|
|
||||||
|
|
||||||
const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
|
|
||||||
['otp', 'password', 'none', 'os', 'proton-pass'];
|
|
||||||
|
|
||||||
let hasPBKDF2Key = false;
|
|
||||||
for (const mode of allSecurityModes) {
|
|
||||||
try {
|
|
||||||
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
|
|
||||||
if (key) {
|
|
||||||
hasPBKDF2Key = true;
|
|
||||||
console.log(`🔐 PBKDF2 key found for mode: ${mode}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Mode non disponible, continuer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasPBKDF2Key) {
|
|
||||||
console.log('🔐 PBKDF2 key exists, navigating to wallet-setup');
|
|
||||||
await navigate('wallet-setup');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aucune clé PBKDF2 trouvée, commencer par la configuration de sécurité
|
|
||||||
console.log('🔐 No PBKDF2 key found, navigating to security-setup');
|
|
||||||
await navigate('security-setup');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error checking storage state:', error);
|
|
||||||
// En cas d'erreur, commencer par la configuration de sécurité
|
|
||||||
console.log('🔐 Error occurred, defaulting to security-setup');
|
|
||||||
await navigate('security-setup');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function navigate(path: string) {
|
export async function navigate(path: string) {
|
||||||
console.log('🧭 Navigate called with path:', path);
|
console.log('🧭 Navigate called with path:', path);
|
||||||
cleanSubscriptions();
|
cleanSubscriptions();
|
||||||
@ -157,12 +77,6 @@ async function handleLocation(path: string) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to initialize home page:', 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 {
|
} else {
|
||||||
console.log('📍 Processing other route:', path);
|
console.log('📍 Processing other route:', path);
|
||||||
const html = await fetch(routeHtml).then(data => data.text());
|
const html = await fetch(routeHtml).then(data => data.text());
|
||||||
@ -224,19 +138,108 @@ export async function init(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
console.log('🚀 Starting application initialization...');
|
console.log('🚀 Starting application initialization...');
|
||||||
|
|
||||||
// Initialiser les services de base
|
// ÉTAPE 1: GESTION DE LA SÉCURITÉ (clés de sécurité) - EN PREMIER
|
||||||
console.log('🔧 Initializing basic services...');
|
console.log('🔐 Step 1: Security key management...');
|
||||||
|
const securityConfigured = await handleSecurityKeyManagement();
|
||||||
|
|
||||||
|
if (!securityConfigured) {
|
||||||
|
console.log('🔐 Security not configured, redirecting to home for setup...');
|
||||||
|
// Naviguer directement vers home pour la configuration de sécurité
|
||||||
|
handleLocation('home');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ÉTAPE 2: INITIALISATION DES SERVICES (seulement après la sécurité)
|
||||||
|
console.log('🔧 Step 2: Initializing services...');
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
(window as any).myService = services;
|
(window as any).myService = services;
|
||||||
const db = await Database.getInstance();
|
const db = await Database.getInstance();
|
||||||
|
|
||||||
// Register service worker
|
// Register service worker and wait for it to be ready
|
||||||
console.log('📱 Registering service worker...');
|
console.log('📱 Registering service worker...');
|
||||||
await db.registerServiceWorker('/src/service-workers/database.worker.js');
|
await db.registerServiceWorker('/src/service-workers/database.worker.js');
|
||||||
|
|
||||||
// Vérifier l'état du storage et naviguer vers la page appropriée
|
// ÉTAPE 3: CONNEXION AUX RELAIS (pour la hauteur de blocs)
|
||||||
console.log('🔍 Checking storage state and navigating to appropriate page...');
|
console.log('🌐 Step 3: Connecting to relays for block height...');
|
||||||
await checkStorageStateAndNavigate();
|
try {
|
||||||
|
console.log('🌐 Connecting to relays...');
|
||||||
|
services.updateUserStatus('🌐 Connecting to blockchain relays...');
|
||||||
|
await services.connectAllRelays();
|
||||||
|
console.log('✅ Relays connected successfully');
|
||||||
|
services.updateUserStatus('✅ Connected to blockchain relays');
|
||||||
|
|
||||||
|
// CRITICAL: Wait for handshake to be processed and block height to be set
|
||||||
|
console.log('⏳ Waiting for relay handshake to complete...');
|
||||||
|
services.updateUserStatus('⏳ Waiting for blockchain synchronization...');
|
||||||
|
await services.waitForBlockHeight();
|
||||||
|
console.log('✅ Block height received from relay');
|
||||||
|
services.updateUserStatus('✅ Blockchain synchronized');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Failed to connect to some relays:', error);
|
||||||
|
console.log('🔄 Continuing despite relay connection issues...');
|
||||||
|
services.updateUserStatus('⚠️ Some relays unavailable, continuing...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ÉTAPE 4: GÉNÉRATION/CHARGEMENT DU WALLET
|
||||||
|
console.log('💰 Step 4: Wallet generation/loading...');
|
||||||
|
const device = await services.getDeviceFromDatabase();
|
||||||
|
console.log('🚀 ~ device:', device);
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
// No wallet exists, create new account
|
||||||
|
console.log('🔍 No existing wallet found, creating new account...');
|
||||||
|
services.updateUserStatus('🔍 Creating new secure wallet...');
|
||||||
|
await services.createNewDevice();
|
||||||
|
services.updateUserStatus('✅ New wallet created successfully');
|
||||||
|
} else {
|
||||||
|
// Wallet exists, restore it and check pairing
|
||||||
|
console.log('🔍 Existing wallet found, restoring account...');
|
||||||
|
services.updateUserStatus('🔍 Restoring existing wallet...');
|
||||||
|
services.restoreDevice(device);
|
||||||
|
services.updateUserStatus('✅ Wallet restored successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Now that block height is set, synchronize wallet
|
||||||
|
console.log('🔄 Synchronizing wallet with blockchain...');
|
||||||
|
services.updateUserStatus('🔄 Synchronizing wallet with blockchain...');
|
||||||
|
await services.updateDeviceBlockHeight();
|
||||||
|
console.log('✅ Wallet synchronization completed');
|
||||||
|
services.updateUserStatus('✅ Wallet synchronized successfully');
|
||||||
|
|
||||||
|
// ÉTAPE 5: HANDSHAKE
|
||||||
|
console.log('🤝 Step 5: Performing handshake...');
|
||||||
|
await performHandshake(services);
|
||||||
|
|
||||||
|
// ÉTAPE 6: PAIRING
|
||||||
|
console.log('🔗 Step 6: Device pairing...');
|
||||||
|
await handlePairing(services);
|
||||||
|
|
||||||
|
// ÉTAPE 7: ÉCOUTE DES PROCESSUS
|
||||||
|
console.log('👂 Step 7: Starting process listening...');
|
||||||
|
await startProcessListening(services);
|
||||||
|
|
||||||
|
// We register all the event listeners if we run in an iframe
|
||||||
|
if (window.self !== window.top) {
|
||||||
|
console.log('🖼️ Registering iframe listeners...');
|
||||||
|
await registerAllListeners();
|
||||||
|
|
||||||
|
// Add iframe mode class for styling
|
||||||
|
document.body.classList.add('iframe-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to appropriate page based on pairing status
|
||||||
|
const isPaired = services.isPaired();
|
||||||
|
console.log('🔍 Device pairing status:', isPaired ? 'Paired' : 'Not paired');
|
||||||
|
|
||||||
|
if (isPaired) {
|
||||||
|
console.log('✅ Device is paired, navigating to account page...');
|
||||||
|
await navigate('account');
|
||||||
|
console.log('✅ Navigation to account completed');
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Device not paired, navigating to home page...');
|
||||||
|
await navigate('home');
|
||||||
|
console.log('✅ Navigation to home completed');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('✅ Application initialization completed successfully');
|
console.log('✅ Application initialization completed successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -251,8 +254,8 @@ export async function init(): Promise<void> {
|
|||||||
alert('⚠️ Insufficient memory for WebAssembly. Please refresh the page or close other tabs.');
|
alert('⚠️ Insufficient memory for WebAssembly. Please refresh the page or close other tabs.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔄 Falling back to security-setup...');
|
console.log('🔄 Falling back to home page...');
|
||||||
await navigate('security-setup');
|
await navigate('home');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -264,4 +264,47 @@ export class EncryptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: DEPRECATED - This method does NOT encrypt, only base64 encoding
|
||||||
|
* Use encryptWithPassword or WebAuthn key encryption instead
|
||||||
|
*/
|
||||||
|
async encryptWithWebAuthn(
|
||||||
|
credentials: CredentialData,
|
||||||
|
credentialId: string
|
||||||
|
): Promise<string> {
|
||||||
|
secureLogger.error('encryptWithWebAuthn is deprecated and does NOT encrypt data', new Error('Use encryptWithPassword or WebAuthn encryption'));
|
||||||
|
|
||||||
|
const data = JSON.stringify({
|
||||||
|
spendKey: credentials.spendKey,
|
||||||
|
scanKey: credentials.scanKey,
|
||||||
|
timestamp: credentials.timestamp
|
||||||
|
});
|
||||||
|
|
||||||
|
// WARNING: Only base64 encoding, no encryption - DO NOT USE FOR SENSITIVE DATA
|
||||||
|
const encoded = btoa(data);
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: DEPRECATED - This method does NOT decrypt, only base64 decoding
|
||||||
|
* Use decryptWithPassword or WebAuthn key decryption instead
|
||||||
|
*/
|
||||||
|
async decryptWithWebAuthn(
|
||||||
|
encryptedData: string,
|
||||||
|
credentialId: string
|
||||||
|
): Promise<CredentialData> {
|
||||||
|
secureLogger.error('decryptWithWebAuthn is deprecated and does NOT decrypt data', new Error('Use decryptWithPassword or WebAuthn decryption'));
|
||||||
|
|
||||||
|
// WARNING: Only base64 decoding, no decryption - DO NOT USE FOR SENSITIVE DATA
|
||||||
|
const decoded = atob(encryptedData);
|
||||||
|
const data = JSON.parse(decoded);
|
||||||
|
|
||||||
|
return {
|
||||||
|
spendKey: data.spendKey,
|
||||||
|
scanKey: data.scanKey,
|
||||||
|
salt: new Uint8Array(0),
|
||||||
|
iterations: 0,
|
||||||
|
timestamp: data.timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,77 @@ export class StorageService {
|
|||||||
return StorageService.instance;
|
return StorageService.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stocke une clé dans le gestionnaire de mots de passe du navigateur
|
||||||
|
*/
|
||||||
|
async storeKeyInBrowser(key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
secureLogger.info('Storing key in browser password manager', {
|
||||||
|
component: 'StorageService',
|
||||||
|
operation: 'storeKeyInBrowser'
|
||||||
|
});
|
||||||
|
|
||||||
|
// IMPORTANT: Ne jamais stocker la clé PBKDF2 en clair !
|
||||||
|
// Utiliser l'API Credential Management pour stocker comme mot de passe
|
||||||
|
// Le navigateur chiffrera automatiquement le mot de passe
|
||||||
|
const credential = new PasswordCredential({
|
||||||
|
id: 'lecoffre-pbkdf2-key',
|
||||||
|
password: key, // Le navigateur chiffre automatiquement ce mot de passe
|
||||||
|
name: 'LeCoffre PBKDF2 Key'
|
||||||
|
});
|
||||||
|
|
||||||
|
await navigator.credentials.store(credential);
|
||||||
|
|
||||||
|
secureLogger.info('Key stored in browser password manager successfully (encrypted by browser)', {
|
||||||
|
component: 'StorageService',
|
||||||
|
operation: 'storeKeyInBrowser'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
secureLogger.error('Failed to store key in browser password manager', {
|
||||||
|
component: 'StorageService',
|
||||||
|
operation: 'storeKeyInBrowser',
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stocke un wallet chiffré
|
||||||
|
*/
|
||||||
|
async storeEncryptedWallet(encryptedWallet: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
secureLogger.info('Storing encrypted wallet', {
|
||||||
|
component: 'StorageService',
|
||||||
|
operation: 'storeEncryptedWallet'
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = await this.openDatabase();
|
||||||
|
const transaction = db.transaction([this.storeName], 'readwrite');
|
||||||
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const request = store.put(encryptedWallet, 'encrypted-wallet');
|
||||||
|
request.onsuccess = () => resolve();
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
secureLogger.info('Encrypted wallet stored successfully', {
|
||||||
|
component: 'StorageService',
|
||||||
|
operation: 'storeEncryptedWallet'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
secureLogger.error('Failed to store encrypted wallet', {
|
||||||
|
component: 'StorageService',
|
||||||
|
operation: 'storeEncryptedWallet',
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stocke une clé en clair (non recommandé)
|
* Stocke une clé en clair (non recommandé)
|
||||||
*/
|
*/
|
||||||
@ -55,92 +126,6 @@ export class StorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Récupère une clé en clair depuis IndexedDB
|
|
||||||
*/
|
|
||||||
async retrievePlainKey(): Promise<string | null> {
|
|
||||||
try {
|
|
||||||
const db = await this.openDatabase();
|
|
||||||
const transaction = db.transaction([this.storeName], 'readonly');
|
|
||||||
const store = transaction.objectStore(this.storeName);
|
|
||||||
|
|
||||||
const result = await new Promise<string | null>((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<string | null> {
|
|
||||||
try {
|
|
||||||
const db = await this.openDatabase();
|
|
||||||
const transaction = db.transaction([this.storeName], 'readonly');
|
|
||||||
const store = transaction.objectStore(this.storeName);
|
|
||||||
|
|
||||||
const result = await new Promise<string | null>((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<void> {
|
|
||||||
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<void>((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
|
* Ouvre la base de données IndexedDB
|
||||||
*/
|
*/
|
||||||
@ -149,8 +134,7 @@ export class StorageService {
|
|||||||
const request = indexedDB.open(this.dbName, this.dbVersion);
|
const request = indexedDB.open(this.dbName, this.dbVersion);
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
const error = new Error('Database open failed');
|
secureLogger.error('Failed to open IndexedDB', new Error('Database open failed'), {
|
||||||
secureLogger.error('Failed to open IndexedDB', error, {
|
|
||||||
component: 'StorageService',
|
component: 'StorageService',
|
||||||
operation: 'openDatabase'
|
operation: 'openDatabase'
|
||||||
});
|
});
|
||||||
|
|||||||
@ -58,11 +58,11 @@ export class SecureCredentialsService {
|
|||||||
let currentMode = await this.securityModeService.getCurrentMode();
|
let currentMode = await this.securityModeService.getCurrentMode();
|
||||||
|
|
||||||
if (!currentMode) {
|
if (!currentMode) {
|
||||||
secureLogger.warn('No security mode selected, using default mode: none', {
|
secureLogger.warn('No security mode selected, using default mode: browser', {
|
||||||
component: 'SecureCredentialsService',
|
component: 'SecureCredentialsService',
|
||||||
operation: 'generateSecureCredentials'
|
operation: 'generateSecureCredentials'
|
||||||
});
|
});
|
||||||
currentMode = 'none';
|
currentMode = 'browser';
|
||||||
await this.securityModeService.setSecurityMode(currentMode);
|
await this.securityModeService.setSecurityMode(currentMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +83,8 @@ export class SecureCredentialsService {
|
|||||||
} else if (modeConfig.implementation.useEncryption) {
|
} else if (modeConfig.implementation.useEncryption) {
|
||||||
if (currentMode === 'password') {
|
if (currentMode === 'password') {
|
||||||
return this.generatePasswordCredentials(password, _options);
|
return this.generatePasswordCredentials(password, _options);
|
||||||
|
} else if (currentMode === 'browser') {
|
||||||
|
return this.generateBrowserCredentials(password, _options);
|
||||||
} else {
|
} else {
|
||||||
return this.generateEncryptedCredentials(password, _options);
|
return this.generateEncryptedCredentials(password, _options);
|
||||||
}
|
}
|
||||||
@ -111,6 +113,10 @@ export class SecureCredentialsService {
|
|||||||
// Récupérer la clé chiffrée avec WebAuthn
|
// Récupérer la clé chiffrée avec WebAuthn
|
||||||
return await webAuthnService.retrieveKeyWithWebAuthn(securityMode);
|
return await webAuthnService.retrieveKeyWithWebAuthn(securityMode);
|
||||||
|
|
||||||
|
case 'browser':
|
||||||
|
// Récupérer la clé du gestionnaire de mots de passe
|
||||||
|
return await storageService.retrieveKeyFromBrowser();
|
||||||
|
|
||||||
case 'otp':
|
case 'otp':
|
||||||
// Récupérer la clé en clair (l'OTP protège l'accès)
|
// Récupérer la clé en clair (l'OTP protège l'accès)
|
||||||
return await storageService.retrievePlainKey();
|
return await storageService.retrievePlainKey();
|
||||||
@ -120,15 +126,8 @@ export class SecureCredentialsService {
|
|||||||
return await storageService.retrieveEncryptedKey();
|
return await storageService.retrieveEncryptedKey();
|
||||||
|
|
||||||
case 'none':
|
case 'none':
|
||||||
// Récupérer la clé chiffrée avec la clé en dur
|
// Récupérer la clé en clair
|
||||||
const encryptedData = await storageService.retrieveEncryptedKey();
|
return await storageService.retrievePlainKey();
|
||||||
if (encryptedData) {
|
|
||||||
const { EncryptionService } = await import('./encryption.service');
|
|
||||||
const encryptionService = EncryptionService.getInstance();
|
|
||||||
const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE';
|
|
||||||
return await encryptionService.decrypt(encryptedData, hardcodedKey);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@ -183,6 +182,12 @@ export class SecureCredentialsService {
|
|||||||
await webAuthnService.storeKeyWithWebAuthn(pbkdf2Key, securityMode);
|
await webAuthnService.storeKeyWithWebAuthn(pbkdf2Key, securityMode);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'browser':
|
||||||
|
// Stocker dans le gestionnaire de mots de passe du navigateur
|
||||||
|
console.log('🔐 Storing PBKDF2 key in browser password manager...');
|
||||||
|
await storageService.storeKeyInBrowser(pbkdf2Key);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'otp':
|
case 'otp':
|
||||||
// Générer un secret OTP pour l'authentification (pas de chiffrement)
|
// Générer un secret OTP pour l'authentification (pas de chiffrement)
|
||||||
console.log('🔐 Setting up OTP authentication for PBKDF2 key...');
|
console.log('🔐 Setting up OTP authentication for PBKDF2 key...');
|
||||||
@ -203,11 +208,9 @@ export class SecureCredentialsService {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'none':
|
case 'none':
|
||||||
// Chiffrer avec une clé déterminée en dur (non sécurisé)
|
// Stockage en clair (non recommandé)
|
||||||
console.log('⚠️ Storing PBKDF2 key with hardcoded encryption (not recommended)...');
|
console.log('⚠️ Storing PBKDF2 key in plain text (not recommended)...');
|
||||||
const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE';
|
await storageService.storePlainKey(pbkdf2Key);
|
||||||
const encryptedKeyNone = await encryptionService.encrypt(pbkdf2Key, hardcodedKey);
|
|
||||||
await storageService.storeEncryptedKey(encryptedKeyNone, securityMode);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -344,6 +347,57 @@ export class SecureCredentialsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère des credentials pour le navigateur
|
||||||
|
*/
|
||||||
|
private async generateBrowserCredentials(
|
||||||
|
password: string,
|
||||||
|
_options: CredentialOptions = {}
|
||||||
|
): Promise<CredentialData> {
|
||||||
|
try {
|
||||||
|
secureLogger.info('Generating browser-saved credentials', {
|
||||||
|
component: 'SecureCredentialsService',
|
||||||
|
operation: 'generateBrowserCredentials'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Import dynamique du service
|
||||||
|
const { EncryptionService } = await import('./encryption.service');
|
||||||
|
const encryptionService = EncryptionService.getInstance();
|
||||||
|
|
||||||
|
// Générer des clés aléatoires
|
||||||
|
const keys = encryptionService.generateRandomKeys();
|
||||||
|
|
||||||
|
// Créer un formulaire temporaire pour déclencher la sauvegarde du navigateur
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.style.cssText = 'position: absolute; left: -9999px; top: -9999px;';
|
||||||
|
form.innerHTML = `
|
||||||
|
<input type="text" name="username" value="4NK_SecureKey" style="display: none;">
|
||||||
|
<input type="password" name="password" value="${keys.spendKey}" style="display: none;">
|
||||||
|
<input type="password" name="scanKey" value="${keys.scanKey}" style="display: none;">
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(form);
|
||||||
|
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
|
||||||
|
form.dispatchEvent(submitEvent);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
document.body.removeChild(form);
|
||||||
|
|
||||||
|
return {
|
||||||
|
spendKey: keys.spendKey,
|
||||||
|
scanKey: keys.scanKey,
|
||||||
|
salt: new Uint8Array(0),
|
||||||
|
iterations: 0,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
secureLogger.error('Failed to generate browser credentials', error as Error, {
|
||||||
|
component: 'SecureCredentialsService',
|
||||||
|
operation: 'generateBrowserCredentials'
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Génère des credentials chiffrés
|
* Génère des credentials chiffrés
|
||||||
*/
|
*/
|
||||||
@ -723,66 +777,4 @@ QR Code URL: ${qrUrl}`);
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Supprime les credentials (alias pour deleteCredentials)
|
|
||||||
*/
|
|
||||||
async deleteCredentials(): Promise<void> {
|
|
||||||
return this.clearCredentials();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Demande un mot de passe à l'utilisateur
|
|
||||||
*/
|
|
||||||
private async promptForPassword(): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const password = prompt('Entrez un mot de passe pour chiffrer la clé PBKDF2:');
|
|
||||||
if (password) {
|
|
||||||
resolve(password);
|
|
||||||
} else {
|
|
||||||
reject(new Error('Password prompt cancelled'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valide la force d'un mot de passe
|
|
||||||
*/
|
|
||||||
validatePasswordStrength(password: string): {
|
|
||||||
isValid: boolean;
|
|
||||||
score: number;
|
|
||||||
feedback: string[];
|
|
||||||
} {
|
|
||||||
const feedback: string[] = [];
|
|
||||||
let score = 0;
|
|
||||||
|
|
||||||
// Vérifications de complexité
|
|
||||||
if (password.length < 8) {
|
|
||||||
feedback.push('Le mot de passe doit contenir au moins 8 caractères');
|
|
||||||
return { isValid: false, score: 0, feedback };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length >= 8) score += 1;
|
|
||||||
if (password.length >= 12) score += 1;
|
|
||||||
if (password.length >= 16) score += 1;
|
|
||||||
|
|
||||||
if (/[a-z]/.test(password)) score += 1;
|
|
||||||
if (/[A-Z]/.test(password)) score += 1;
|
|
||||||
if (/[0-9]/.test(password)) score += 1;
|
|
||||||
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
|
|
||||||
|
|
||||||
// Feedback détaillé
|
|
||||||
if (score < 3) {
|
|
||||||
feedback.push('Mot de passe faible : ajoutez des majuscules, chiffres et caractères spéciaux');
|
|
||||||
} else if (score < 5) {
|
|
||||||
feedback.push('Mot de passe moyen : renforcez avec plus de complexité');
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = score >= 3;
|
|
||||||
|
|
||||||
return { isValid, score, feedback };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export instance pour compatibilité
|
|
||||||
export const secureCredentialsService = SecureCredentialsService.getInstance();
|
|
||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { secureLogger } from './secure-logger';
|
import { secureLogger } from './secure-logger';
|
||||||
|
|
||||||
export type SecurityMode = 'proton-pass' | 'os' | 'otp' | '2fa' | 'password' | 'none';
|
export type SecurityMode = 'proton-pass' | 'os' | 'browser' | 'otp' | '2fa' | 'password' | 'none';
|
||||||
|
|
||||||
export interface SecurityModeConfig {
|
export interface SecurityModeConfig {
|
||||||
mode: SecurityMode;
|
mode: SecurityMode;
|
||||||
@ -20,8 +20,6 @@ export interface SecurityModeConfig {
|
|||||||
useEncryption: boolean;
|
useEncryption: boolean;
|
||||||
usePlatformAuth: boolean;
|
usePlatformAuth: boolean;
|
||||||
storageType: 'encrypted' | 'plain' | 'hybrid';
|
storageType: 'encrypted' | 'plain' | 'hybrid';
|
||||||
requiresOTP?: boolean;
|
|
||||||
requiresPassword?: boolean;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +124,20 @@ export class SecurityModeService {
|
|||||||
storageType: 'encrypted'
|
storageType: 'encrypted'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'browser': {
|
||||||
|
mode: 'browser',
|
||||||
|
name: 'Navigateur',
|
||||||
|
description: 'Utilise les fonctionnalités de sécurité du navigateur',
|
||||||
|
securityLevel: 'medium',
|
||||||
|
requiresConfirmation: false,
|
||||||
|
warnings: [],
|
||||||
|
implementation: {
|
||||||
|
useWebAuthn: true,
|
||||||
|
useEncryption: true,
|
||||||
|
usePlatformAuth: false,
|
||||||
|
storageType: 'encrypted'
|
||||||
|
}
|
||||||
|
},
|
||||||
'otp': {
|
'otp': {
|
||||||
mode: 'otp',
|
mode: 'otp',
|
||||||
name: 'OTP (Proton Pass Compatible)',
|
name: 'OTP (Proton Pass Compatible)',
|
||||||
@ -181,20 +193,19 @@ export class SecurityModeService {
|
|||||||
'none': {
|
'none': {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
name: 'Aucune Sécurité',
|
name: 'Aucune Sécurité',
|
||||||
description: 'Chiffrement avec une clé déterminée en dur (non sécurisé)',
|
description: 'Stockage en clair sans aucune protection',
|
||||||
securityLevel: 'critical',
|
securityLevel: 'critical',
|
||||||
requiresConfirmation: true,
|
requiresConfirmation: true,
|
||||||
warnings: [
|
warnings: [
|
||||||
'🚨 Clé de chiffrement en dur',
|
'🚨 Clés stockées en clair',
|
||||||
'🚨 Accès non protégé',
|
'🚨 Accès non protégé',
|
||||||
'🚨 RISQUE ÉLEVÉ'
|
'🚨 RISQUE ÉLEVÉ'
|
||||||
],
|
],
|
||||||
implementation: {
|
implementation: {
|
||||||
useWebAuthn: false,
|
useWebAuthn: false,
|
||||||
useEncryption: true,
|
useEncryption: false,
|
||||||
usePlatformAuth: false,
|
usePlatformAuth: false,
|
||||||
storageType: 'encrypted',
|
storageType: 'plain'
|
||||||
requiresPassword: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -233,14 +244,14 @@ export class SecurityModeService {
|
|||||||
* Récupère tous les modes disponibles
|
* Récupère tous les modes disponibles
|
||||||
*/
|
*/
|
||||||
public getAvailableModes(): SecurityMode[] {
|
public getAvailableModes(): SecurityMode[] {
|
||||||
return ['proton-pass', 'os', 'otp', '2fa', 'password', 'none'];
|
return ['proton-pass', 'os', 'browser', '2fa', 'none'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les modes recommandés (sécurisés)
|
* Récupère les modes recommandés (sécurisés)
|
||||||
*/
|
*/
|
||||||
public getRecommendedModes(): SecurityMode[] {
|
public getRecommendedModes(): SecurityMode[] {
|
||||||
return ['proton-pass', 'os', 'otp'];
|
return ['proton-pass', 'os', 'browser'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { eventBus } from './event-bus';
|
|||||||
import { secureLogger } from './secure-logger';
|
import { secureLogger } from './secure-logger';
|
||||||
import { memoryManager } from './memory-manager';
|
import { memoryManager } from './memory-manager';
|
||||||
import { secureKeyManager } from './secure-key-manager';
|
import { secureKeyManager } from './secure-key-manager';
|
||||||
import { SecureCredentialsService, secureCredentialsService } from './secure-credentials.service';
|
import { SecureCredentialsService } from './secure-credentials.service';
|
||||||
import Database from './database.service';
|
import Database from './database.service';
|
||||||
|
|
||||||
export interface ServiceContainer {
|
export interface ServiceContainer {
|
||||||
|
|||||||
@ -1841,16 +1841,16 @@ export default class Services {
|
|||||||
if (dbRes['encrypted_device']) {
|
if (dbRes['encrypted_device']) {
|
||||||
// New encrypted format - need to decrypt
|
// New encrypted format - need to decrypt
|
||||||
console.log('🔐 Wallet found in encrypted format, decrypting...');
|
console.log('🔐 Wallet found in encrypted format, decrypting...');
|
||||||
|
|
||||||
// Get the PBKDF2 key based on security mode
|
// Get the PBKDF2 key based on security mode
|
||||||
const { SecureCredentialsService } = await import('./secure-credentials.service');
|
const { SecureCredentialsService } = await import('./secure-credentials.service');
|
||||||
const secureCredentialsService = SecureCredentialsService.getInstance();
|
const secureCredentialsService = SecureCredentialsService.getInstance();
|
||||||
|
|
||||||
// Get all security modes to find which one works
|
// Get all security modes to find which one works
|
||||||
const allSecurityModes = ['otp', 'password', 'none', 'os', 'proton-pass'];
|
const allSecurityModes = ['browser', 'otp', 'password', 'none', 'os', 'proton-pass'];
|
||||||
let pbkdf2Key: string | null = null;
|
let pbkdf2Key: string | null = null;
|
||||||
let workingMode: string | null = null;
|
let workingMode: string | null = null;
|
||||||
|
|
||||||
for (const mode of allSecurityModes) {
|
for (const mode of allSecurityModes) {
|
||||||
try {
|
try {
|
||||||
const key = await secureCredentialsService.retrievePBKDF2Key(mode as any);
|
const key = await secureCredentialsService.retrievePBKDF2Key(mode as any);
|
||||||
@ -1863,20 +1863,20 @@ export default class Services {
|
|||||||
// Continue to next mode
|
// Continue to next mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pbkdf2Key) {
|
if (!pbkdf2Key) {
|
||||||
throw new Error('Failed to retrieve PBKDF2 key - cannot decrypt wallet');
|
throw new Error('Failed to retrieve PBKDF2 key - cannot decrypt wallet');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the device
|
// Decrypt the device
|
||||||
const { EncryptionService } = await import('./encryption.service');
|
const { EncryptionService } = await import('./encryption.service');
|
||||||
const encryptionService = EncryptionService.getInstance();
|
const encryptionService = EncryptionService.getInstance();
|
||||||
|
|
||||||
const decryptedDeviceString = await encryptionService.decrypt(
|
const decryptedDeviceString = await encryptionService.decrypt(
|
||||||
dbRes['encrypted_device'],
|
dbRes['encrypted_device'],
|
||||||
pbkdf2Key
|
pbkdf2Key
|
||||||
);
|
);
|
||||||
|
|
||||||
const decryptedDevice = JSON.parse(decryptedDeviceString);
|
const decryptedDevice = JSON.parse(decryptedDeviceString);
|
||||||
console.log('✅ Wallet decrypted successfully');
|
console.log('✅ Wallet decrypted successfully');
|
||||||
return decryptedDevice;
|
return decryptedDevice;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user