Compare commits

..

10 Commits

Author SHA1 Message Date
0c883dfcac feat: Améliorer la gestion WebAuthn avec détection du contexte sécurisé
- Ajouter vérification du contexte sécurisé (HTTPS) pour WebAuthn
- Implémenter fallback pour le développement HTTP local
- Améliorer les messages d'interface pour expliquer le mode WebAuthn
- Ajouter logs informatifs pour le debugging WebAuthn
- Gestion d'erreur robuste avec fallback automatique
2025-10-23 14:03:52 +02:00
770a5b7397 fix: Rendre la clé maître extractable pour la dérivation HMAC
- Changer extractable: false à extractable: true dans deriveMasterKey
- Résoudre l'erreur 'key is not extractable' lors de l'export de la clé
- Permettre l'utilisation de la clé maître pour dériver les clés spend et scan
- Maintenir la sécurité tout en permettant l'extraction nécessaire
2025-10-23 14:01:18 +02:00
451a1941dc fix: Corriger l'erreur d'algorithme dans la dérivation des clés
- Remplacer deriveBits PBKDF2 par HMAC pour dériver les clés spend et scan
- Résoudre l'erreur 'key.algorithm does not match that of operation'
- Utiliser HMAC-SHA256 avec la clé maître pour dériver les clés spécifiques
- Maintenir la sécurité cryptographique avec une approche compatible
2025-10-23 13:59:50 +02:00
47c90093e3 feat: Intégrer PBKDF2 et WebAuthn dans le processus de pairing
- Ajouter l'initialisation des credentials sécurisés avec PBKDF2
- Déclencher la popup du navigateur pour WebAuthn pendant le pairing
- Mettre à jour l'interface avec les statuts de sécurité
- Utiliser les credentials du navigateur pour sécuriser les clés
- Gestion d'erreur avec fallback si WebAuthn échoue
2025-10-23 13:55:33 +02:00
c1ba781ca5 fix: Corriger les erreurs de pairing et d'interface
- Exporter updateCreatorStatus pour corriger l'erreur 'is not a function'
- Ajouter vérification null dans getProcess pour éviter les erreurs IndexedDB
- Corriger l'appel WASM request_data en convertissant les objets en strings
- Améliorer la gestion d'erreur dans requestDataFromPeers
- Résoudre les erreurs de synchronisation du processus de pairing
2025-10-23 13:50:40 +02:00
0cf6abdcd5 feat: Améliorer l'interface utilisateur du processus de pairing
- Afficher l'adresse du créateur dans l'interface: 'Creator address: tsp1...'
- Afficher le statut d'attente des relays: ' Waiting for relays to be ready...'
- Mettre à jour l'UI en temps réel pendant le processus de pairing
- Améliorer l'expérience utilisateur avec des messages informatifs
2025-10-23 13:36:41 +02:00
7444f64394 fix: Optimiser le memory manager et ajouter l'initialisation PBKDF2
- Réduire la fréquence de monitoring de la mémoire (30s → 2min)
- Éviter le nettoyage en boucle du memory manager
- Ajouter l'initialisation du service PBKDF2 dans Services
- Améliorer les logs pour le debugging du service PBKDF2
2025-10-23 13:31:40 +02:00
1cccf236bb fix: Filtrer les messages d'extensions de navigateur et améliorer le debugging
- Ajouter le filtrage des messages Pass:: et PassClientScriptReady
- Améliorer les logs de debugging pour l'initialisation de la page d'accueil
- Éviter le spam de logs des gestionnaires de mots de passe
- Faciliter le diagnostic des problèmes d'initialisation
2025-10-23 13:17:45 +02:00
0b94cda76e fix: Corriger les erreurs d'initialisation et de communication
- Supprimer l'appel à initEssentialFunctions (fonction supprimée)
- Ajouter le support pour le message IFRAME_READY
- Améliorer les logs de validation WebSocket pour le debugging
- Résoudre les erreurs ReferenceError et Unknown message type
2025-10-23 13:11:45 +02:00
69424c6bf6 fix: Corriger le spam de logs React DevTools
- Déplacer le filtrage des messages d'extension avant le logging
- Éviter les logs en boucle des messages react-devtools-content-script
- Améliorer la performance en filtrant d'abord, puis en loggant
2025-10-23 13:10:12 +02:00
9 changed files with 175 additions and 66 deletions

View File

@ -28,17 +28,19 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.38.0", "@eslint/js": "^9.38.0",
"@testing-library/jest-dom": "^6.1.4",
"@types/jest": "^29.5.8",
"@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2", "@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0", "eslint": "^9.38.0",
"jest": "^29.7.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"ts-jest": "^29.1.1",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.4.11", "vite": "^5.4.11",
"vite-plugin-static-copy": "^1.0.6", "vite-plugin-static-copy": "^1.0.6",
"jest": "^29.7.0", "vite-plugin-top-level-await": "^1.6.0",
"ts-jest": "^29.1.1", "vite-plugin-wasm": "^3.5.0"
"@types/jest": "^29.5.8",
"@testing-library/jest-dom": "^6.1.4"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.8", "axios": "^1.7.8",
@ -47,7 +49,6 @@
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"sweetalert2": "^11.14.5", "sweetalert2": "^11.14.5",
"vite-plugin-copy": "^0.1.6", "vite-plugin-copy": "^0.1.6",
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "^3.2.2"
"vite-plugin-wasm": "^3.3.0"
} }
} }

View File

@ -132,15 +132,21 @@ export async function initHomePage(): Promise<void> {
}); });
try { try {
console.log('🔧 Getting services instance...');
const service = await Services.getInstance(); const service = await Services.getInstance();
console.log('🔧 Getting device address...');
const spAddress = await service.getDeviceAddress(); const spAddress = await service.getDeviceAddress();
console.log('🔧 Generating create button...');
generateCreateBtn(); generateCreateBtn();
console.log('🔧 Displaying emojis...');
displayEmojis(spAddress); displayEmojis(spAddress);
// Hide loading spinner after initialization // Hide loading spinner after initialization
console.log('🔧 Hiding loading spinner...');
hideHomeLoadingSpinner(); hideHomeLoadingSpinner();
console.log('✅ Home page initialization completed');
} catch (error) { } catch (error) {
console.error('Error initializing home page:', error); console.error('Error initializing home page:', error);
hideHomeLoadingSpinner(); hideHomeLoadingSpinner();
throw error; throw error;
} }
@ -354,20 +360,22 @@ export function initContentMenu() {
export function initIframeCommunication() { export function initIframeCommunication() {
// Listen for messages from parent window // Listen for messages from parent window
window.addEventListener('message', event => { window.addEventListener('message', event => {
// Filter out browser extension messages first
if (
event.data.source === 'react-devtools-content-script' ||
event.data.hello === true ||
!event.data.type ||
event.data.type.startsWith('Pass::') ||
event.data.type === 'PassClientScriptReady'
) {
return; // Ignore browser extension messages
}
// Security check - in production, verify event.origin // Security check - in production, verify event.origin
console.log('📨 Received message from parent:', event.data); console.log('📨 Received message from parent:', event.data);
const { type, data } = event.data; const { type, data } = event.data;
// Filter out browser extension messages
if (
event.data.source === 'react-devtools-content-script' ||
event.data.hello === true ||
!type
) {
return; // Ignore browser extension messages
}
switch (type) { switch (type) {
case 'TEST_MESSAGE': case 'TEST_MESSAGE':
console.log('🧪 Test message received:', data.message); console.log('🧪 Test message received:', data.message);
@ -397,6 +405,10 @@ export function initIframeCommunication() {
console.log('👂 Parent is listening for messages'); console.log('👂 Parent is listening for messages');
break; break;
case 'IFRAME_READY':
console.log('✅ Iframe is ready and initialized');
break;
default: default:
console.log('❓ Unknown message type from parent:', type); console.log('❓ Unknown message type from parent:', type);
} }

View File

@ -61,8 +61,7 @@ async function handleLocation(path: string) {
await new Promise(requestAnimationFrame); await new Promise(requestAnimationFrame);
// Initialize essential functions // Essential functions are now handled directly in the application
await initEssentialFunctions();
// const modalService = await ModalService.getInstance() // const modalService = await ModalService.getInstance()
// modalService.injectValidationModal() // modalService.injectValidationModal()

View File

@ -45,10 +45,10 @@ export class MemoryManager {
this.isMonitoring = true; this.isMonitoring = true;
this.logMemoryStats(); this.logMemoryStats();
// Vérifier la mémoire toutes les 30 secondes // Vérifier la mémoire toutes les 2 minutes
setInterval(() => { setInterval(() => {
this.checkMemoryUsage(); this.checkMemoryUsage();
}, 30000); }, 120000);
} }
/** /**

View File

@ -104,28 +104,55 @@ export class SecureCredentialsService {
const encryptedSpendKey = await this.encryptKey(credentialData.spendKey, masterKey); const encryptedSpendKey = await this.encryptKey(credentialData.spendKey, masterKey);
const encryptedScanKey = await this.encryptKey(credentialData.scanKey, masterKey); const encryptedScanKey = await this.encryptKey(credentialData.scanKey, masterKey);
// Stocker dans les credentials du navigateur // Vérifier si WebAuthn est disponible et si on est en HTTPS
const credential = await navigator.credentials.create({ const isSecureContext = window.isSecureContext;
publicKey: { const hasWebAuthn = navigator.credentials && navigator.credentials.create;
challenge: new Uint8Array(32),
rp: { name: '4NK Secure Storage' }, let credential = null;
user: {
id: new TextEncoder().encode('4nk-user'), if (isSecureContext && hasWebAuthn) {
name: '4NK User', // Stocker dans les credentials du navigateur (HTTPS requis)
displayName: '4NK User' try {
}, credential = await navigator.credentials.create({
pubKeyCredParams: [ publicKey: {
{ type: 'public-key', alg: -7 }, // ES256 challenge: new Uint8Array(32),
{ type: 'public-key', alg: -257 } // RS256 rp: { name: '4NK Secure Storage' },
], user: {
authenticatorSelection: { id: new TextEncoder().encode('4nk-user'),
authenticatorAttachment: 'platform', name: '4NK User',
userVerification: 'required' displayName: '4NK User'
}, },
timeout: 60000, pubKeyCredParams: [
attestation: 'direct' { type: 'public-key', alg: -7 }, // ES256
{ type: 'public-key', alg: -257 } // RS256
],
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required'
},
timeout: 60000,
attestation: 'direct'
}
});
secureLogger.info('WebAuthn credential created successfully', {
component: 'SecureCredentialsService',
operation: 'webauthn_create'
});
} catch (error) {
secureLogger.warn('WebAuthn credential creation failed, using fallback', error as Error, {
component: 'SecureCredentialsService',
operation: 'webauthn_create'
});
} }
}); } else {
secureLogger.info('WebAuthn not available (HTTP context), using fallback storage', {
component: 'SecureCredentialsService',
operation: 'webauthn_fallback',
isSecureContext,
hasWebAuthn
});
}
if (credential) { if (credential) {
// Stocker les données chiffrées dans IndexedDB // Stocker les données chiffrées dans IndexedDB
@ -261,7 +288,7 @@ export class SecureCredentialsService {
}, },
keyMaterial, keyMaterial,
{ name: 'AES-GCM', length: 256 }, { name: 'AES-GCM', length: 256 },
false, true, // Make key extractable
['encrypt', 'decrypt'] ['encrypt', 'decrypt']
); );
} }
@ -272,15 +299,19 @@ export class SecureCredentialsService {
private async deriveSpendKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> { private async deriveSpendKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> {
const spendSalt = new Uint8Array([...salt, 0x73, 0x70, 0x65, 0x6e, 0x64]); // "spend" const spendSalt = new Uint8Array([...salt, 0x73, 0x70, 0x65, 0x6e, 0x64]); // "spend"
const spendKeyMaterial = await crypto.subtle.deriveBits( // Use HMAC with the master key to derive spend key
{ const hmacKey = await crypto.subtle.importKey(
name: 'PBKDF2', 'raw',
salt: spendSalt, await crypto.subtle.exportKey('raw', masterKey),
iterations: 1000, { name: 'HMAC', hash: 'SHA-256' },
hash: 'SHA-256' false,
}, ['sign']
masterKey, );
256
const spendKeyMaterial = await crypto.subtle.sign(
'HMAC',
hmacKey,
spendSalt
); );
return Array.from(new Uint8Array(spendKeyMaterial)) return Array.from(new Uint8Array(spendKeyMaterial))
@ -294,15 +325,19 @@ export class SecureCredentialsService {
private async deriveScanKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> { private async deriveScanKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> {
const scanSalt = new Uint8Array([...salt, 0x73, 0x63, 0x61, 0x6e]); // "scan" const scanSalt = new Uint8Array([...salt, 0x73, 0x63, 0x61, 0x6e]); // "scan"
const scanKeyMaterial = await crypto.subtle.deriveBits( // Use HMAC with the master key to derive scan key
{ const hmacKey = await crypto.subtle.importKey(
name: 'PBKDF2', 'raw',
salt: scanSalt, await crypto.subtle.exportKey('raw', masterKey),
iterations: 1000, { name: 'HMAC', hash: 'SHA-256' },
hash: 'SHA-256' false,
}, ['sign']
masterKey, );
256
const scanKeyMaterial = await crypto.subtle.sign(
'HMAC',
hmacKey,
scanSalt
); );
return Array.from(new Uint8Array(scanKeyMaterial)) return Array.from(new Uint8Array(scanKeyMaterial))

View File

@ -195,6 +195,20 @@ export default class Services {
// Nettoyer les caches périodiquement // Nettoyer les caches périodiquement
this.startCacheCleanup(); this.startCacheCleanup();
// Initialiser le service PBKDF2 pour les credentials sécurisés
try {
const { secureCredentialsService } = await import('./secure-credentials.service');
secureLogger.info('PBKDF2 service initialized for secure credentials', {
component: 'Services',
operation: 'pbkdf2_init'
});
} catch (error) {
secureLogger.warn('Failed to initialize PBKDF2 service', error as Error, {
component: 'Services',
operation: 'pbkdf2_init'
});
}
secureLogger.info('Services initialized', { secureLogger.info('Services initialized', {
component: 'Services', component: 'Services',
operation: 'initialization' operation: 'initialization'
@ -686,6 +700,10 @@ export default class Services {
if (!relayAddress) { if (!relayAddress) {
console.log('⏳ Waiting for relays to be ready...'); console.log('⏳ Waiting for relays to be ready...');
// Update UI status
const { updateCreatorStatus } = await import('../utils/sp-address.utils');
updateCreatorStatus('⏳ Waiting for relays to be ready...');
await this.getRelayReadyPromise(); await this.getRelayReadyPromise();
relayAddress = this.getAllRelays()[0]?.spAddress; relayAddress = this.getAllRelays()[0]?.spAddress;
} }
@ -1656,6 +1674,10 @@ export default class Services {
} }
public async getProcess(processId: string): Promise<Process | null> { public async getProcess(processId: string): Promise<Process | null> {
if (!processId) {
return null;
}
if (this.processesCache[processId]) { if (this.processesCache[processId]) {
return this.processesCache[processId]; return this.processesCache[processId];
} else { } else {
@ -2267,10 +2289,16 @@ export default class Services {
console.log('Requesting data from peers'); console.log('Requesting data from peers');
const membersList = this.getAllMembers(); const membersList = this.getAllMembers();
try { try {
const res = this.sdkClient.request_data(processId, stateIds, roles, membersList); // Convert objects to strings for WASM compatibility
const rolesString = JSON.stringify(roles);
const membersString = JSON.stringify(membersList);
const stateIdsString = JSON.stringify(stateIds);
const res = this.sdkClient.request_data(processId, stateIdsString, rolesString, membersString);
await this.handleApiReturn(res); await this.handleApiReturn(res);
} catch (e) { } catch (e) {
console.error(e); console.error('Error requesting data from peers:', e);
throw e;
} }
} }

View File

@ -2444,7 +2444,7 @@ function handleWordsInput() {
} }
// Update creator status // Update creator status
function updateCreatorStatus(message: string, isError: boolean = false) { export function updateCreatorStatus(message: string, isError: boolean = false) {
const container = getCorrectDOM('login-4nk-component') as HTMLElement; const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const statusElement = container.querySelector('#creator-status'); const statusElement = container.querySelector('#creator-status');
if (statusElement) { if (statusElement) {
@ -2697,6 +2697,34 @@ export async function prepareAndSendPairingTx(): Promise<void> {
console.log(`🔍 DEBUG: Creator address: ${creatorAddress}`); console.log(`🔍 DEBUG: Creator address: ${creatorAddress}`);
// Update UI with creator address
updateCreatorStatus(`Creator address: ${creatorAddress}`);
// Initialize secure credentials with PBKDF2 and browser credentials
try {
const { secureCredentialsService } = await import('../services/secure-credentials.service');
// Check if we're in a secure context (HTTPS)
if (window.isSecureContext) {
updateCreatorStatus('🔐 Initializing secure credentials with browser...');
} else {
updateCreatorStatus('🔐 Initializing secure credentials (HTTP mode - WebAuthn not available)...');
}
// This will trigger the browser popup for WebAuthn (only in HTTPS)
const credentials = await secureCredentialsService.generateSecureCredentials('4nk-pairing-password');
console.log('✅ Secure credentials initialized with PBKDF2 and WebAuthn');
if (window.isSecureContext) {
updateCreatorStatus('✅ Secure credentials ready (WebAuthn enabled)');
} else {
updateCreatorStatus('✅ Secure credentials ready (fallback mode - use HTTPS for WebAuthn)');
}
} catch (error) {
console.warn('⚠️ Secure credentials initialization failed:', error);
updateCreatorStatus('⚠️ Using fallback credentials');
}
// Create pairing process with creator's address // Create pairing process with creator's address
const createPairingProcessReturn = await service.createPairingProcess( const createPairingProcessReturn = await service.createPairingProcess(
creatorAddress, // Use creator's address as memberPublicName creatorAddress, // Use creator's address as memberPublicName

View File

@ -34,7 +34,8 @@ export async function initWebsocket(url: string) {
secureLogger.warn('Invalid WebSocket message received', { secureLogger.warn('Invalid WebSocket message received', {
component: 'WebSocket', component: 'WebSocket',
operation: 'message_validation', operation: 'message_validation',
errors: validation.errors errors: validation.errors,
messagePreview: msgData.substring(0, 100) // Log first 100 chars for debugging
}); });
return; return;
} }

View File

@ -1,11 +1,16 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import path from 'path' import path from 'path';
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default defineConfig({ export default defineConfig({
optimizeDeps: { optimizeDeps: {
include: ['qrcode'] include: ['qrcode']
}, },
plugins: [], plugins: [
wasm(),
topLevelAwait()
],
build: { build: {
outDir: 'dist', outDir: 'dist',
target: 'esnext', target: 'esnext',