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