Compare commits
10 Commits
b545e3875e
...
0c883dfcac
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c883dfcac | |||
| 770a5b7397 | |||
| 451a1941dc | |||
| 47c90093e3 | |||
| c1ba781ca5 | |||
| 0cf6abdcd5 | |||
| 7444f64394 | |||
| 1cccf236bb | |||
| 0b94cda76e | |||
| 69424c6bf6 |
13
package.json
13
package.json
@ -28,17 +28,19 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.38.0",
|
"@eslint/js": "^9.38.0",
|
||||||
|
"@testing-library/jest-dom": "^6.1.4",
|
||||||
|
"@types/jest": "^29.5.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||||
"@typescript-eslint/parser": "^8.46.2",
|
"@typescript-eslint/parser": "^8.46.2",
|
||||||
"eslint": "^9.38.0",
|
"eslint": "^9.38.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
|
"ts-jest": "^29.1.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.4.11",
|
"vite": "^5.4.11",
|
||||||
"vite-plugin-static-copy": "^1.0.6",
|
"vite-plugin-static-copy": "^1.0.6",
|
||||||
"jest": "^29.7.0",
|
"vite-plugin-top-level-await": "^1.6.0",
|
||||||
"ts-jest": "^29.1.1",
|
"vite-plugin-wasm": "^3.5.0"
|
||||||
"@types/jest": "^29.5.8",
|
|
||||||
"@testing-library/jest-dom": "^6.1.4"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
@ -47,7 +49,6 @@
|
|||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"sweetalert2": "^11.14.5",
|
"sweetalert2": "^11.14.5",
|
||||||
"vite-plugin-copy": "^0.1.6",
|
"vite-plugin-copy": "^0.1.6",
|
||||||
"vite-plugin-html": "^3.2.2",
|
"vite-plugin-html": "^3.2.2"
|
||||||
"vite-plugin-wasm": "^3.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,15 +132,21 @@ export async function initHomePage(): Promise<void> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('🔧 Getting services instance...');
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
|
console.log('🔧 Getting device address...');
|
||||||
const spAddress = await service.getDeviceAddress();
|
const spAddress = await service.getDeviceAddress();
|
||||||
|
console.log('🔧 Generating create button...');
|
||||||
generateCreateBtn();
|
generateCreateBtn();
|
||||||
|
console.log('🔧 Displaying emojis...');
|
||||||
displayEmojis(spAddress);
|
displayEmojis(spAddress);
|
||||||
|
|
||||||
// Hide loading spinner after initialization
|
// Hide loading spinner after initialization
|
||||||
|
console.log('🔧 Hiding loading spinner...');
|
||||||
hideHomeLoadingSpinner();
|
hideHomeLoadingSpinner();
|
||||||
|
console.log('✅ Home page initialization completed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing home page:', error);
|
console.error('❌ Error initializing home page:', error);
|
||||||
hideHomeLoadingSpinner();
|
hideHomeLoadingSpinner();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -354,20 +360,22 @@ export function initContentMenu() {
|
|||||||
export function initIframeCommunication() {
|
export function initIframeCommunication() {
|
||||||
// Listen for messages from parent window
|
// Listen for messages from parent window
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
|
// Filter out browser extension messages first
|
||||||
|
if (
|
||||||
|
event.data.source === 'react-devtools-content-script' ||
|
||||||
|
event.data.hello === true ||
|
||||||
|
!event.data.type ||
|
||||||
|
event.data.type.startsWith('Pass::') ||
|
||||||
|
event.data.type === 'PassClientScriptReady'
|
||||||
|
) {
|
||||||
|
return; // Ignore browser extension messages
|
||||||
|
}
|
||||||
|
|
||||||
// Security check - in production, verify event.origin
|
// Security check - in production, verify event.origin
|
||||||
console.log('📨 Received message from parent:', event.data);
|
console.log('📨 Received message from parent:', event.data);
|
||||||
|
|
||||||
const { type, data } = event.data;
|
const { type, data } = event.data;
|
||||||
|
|
||||||
// Filter out browser extension messages
|
|
||||||
if (
|
|
||||||
event.data.source === 'react-devtools-content-script' ||
|
|
||||||
event.data.hello === true ||
|
|
||||||
!type
|
|
||||||
) {
|
|
||||||
return; // Ignore browser extension messages
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'TEST_MESSAGE':
|
case 'TEST_MESSAGE':
|
||||||
console.log('🧪 Test message received:', data.message);
|
console.log('🧪 Test message received:', data.message);
|
||||||
@ -397,6 +405,10 @@ export function initIframeCommunication() {
|
|||||||
console.log('👂 Parent is listening for messages');
|
console.log('👂 Parent is listening for messages');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'IFRAME_READY':
|
||||||
|
console.log('✅ Iframe is ready and initialized');
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log('❓ Unknown message type from parent:', type);
|
console.log('❓ Unknown message type from parent:', type);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,8 +61,7 @@ async function handleLocation(path: string) {
|
|||||||
|
|
||||||
await new Promise(requestAnimationFrame);
|
await new Promise(requestAnimationFrame);
|
||||||
|
|
||||||
// Initialize essential functions
|
// Essential functions are now handled directly in the application
|
||||||
await initEssentialFunctions();
|
|
||||||
|
|
||||||
// const modalService = await ModalService.getInstance()
|
// const modalService = await ModalService.getInstance()
|
||||||
// modalService.injectValidationModal()
|
// modalService.injectValidationModal()
|
||||||
|
|||||||
@ -45,10 +45,10 @@ export class MemoryManager {
|
|||||||
this.isMonitoring = true;
|
this.isMonitoring = true;
|
||||||
this.logMemoryStats();
|
this.logMemoryStats();
|
||||||
|
|
||||||
// Vérifier la mémoire toutes les 30 secondes
|
// Vérifier la mémoire toutes les 2 minutes
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.checkMemoryUsage();
|
this.checkMemoryUsage();
|
||||||
}, 30000);
|
}, 120000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -104,8 +104,16 @@ export class SecureCredentialsService {
|
|||||||
const encryptedSpendKey = await this.encryptKey(credentialData.spendKey, masterKey);
|
const encryptedSpendKey = await this.encryptKey(credentialData.spendKey, masterKey);
|
||||||
const encryptedScanKey = await this.encryptKey(credentialData.scanKey, masterKey);
|
const encryptedScanKey = await this.encryptKey(credentialData.scanKey, masterKey);
|
||||||
|
|
||||||
// Stocker dans les credentials du navigateur
|
// Vérifier si WebAuthn est disponible et si on est en HTTPS
|
||||||
const credential = await navigator.credentials.create({
|
const isSecureContext = window.isSecureContext;
|
||||||
|
const hasWebAuthn = navigator.credentials && navigator.credentials.create;
|
||||||
|
|
||||||
|
let credential = null;
|
||||||
|
|
||||||
|
if (isSecureContext && hasWebAuthn) {
|
||||||
|
// Stocker dans les credentials du navigateur (HTTPS requis)
|
||||||
|
try {
|
||||||
|
credential = await navigator.credentials.create({
|
||||||
publicKey: {
|
publicKey: {
|
||||||
challenge: new Uint8Array(32),
|
challenge: new Uint8Array(32),
|
||||||
rp: { name: '4NK Secure Storage' },
|
rp: { name: '4NK Secure Storage' },
|
||||||
@ -127,6 +135,25 @@ export class SecureCredentialsService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
secureLogger.info('WebAuthn credential created successfully', {
|
||||||
|
component: 'SecureCredentialsService',
|
||||||
|
operation: 'webauthn_create'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
secureLogger.warn('WebAuthn credential creation failed, using fallback', error as Error, {
|
||||||
|
component: 'SecureCredentialsService',
|
||||||
|
operation: 'webauthn_create'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
secureLogger.info('WebAuthn not available (HTTP context), using fallback storage', {
|
||||||
|
component: 'SecureCredentialsService',
|
||||||
|
operation: 'webauthn_fallback',
|
||||||
|
isSecureContext,
|
||||||
|
hasWebAuthn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (credential) {
|
if (credential) {
|
||||||
// Stocker les données chiffrées dans IndexedDB
|
// Stocker les données chiffrées dans IndexedDB
|
||||||
await this.storeEncryptedCredentials({
|
await this.storeEncryptedCredentials({
|
||||||
@ -261,7 +288,7 @@ export class SecureCredentialsService {
|
|||||||
},
|
},
|
||||||
keyMaterial,
|
keyMaterial,
|
||||||
{ name: 'AES-GCM', length: 256 },
|
{ name: 'AES-GCM', length: 256 },
|
||||||
false,
|
true, // Make key extractable
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -272,15 +299,19 @@ export class SecureCredentialsService {
|
|||||||
private async deriveSpendKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> {
|
private async deriveSpendKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> {
|
||||||
const spendSalt = new Uint8Array([...salt, 0x73, 0x70, 0x65, 0x6e, 0x64]); // "spend"
|
const spendSalt = new Uint8Array([...salt, 0x73, 0x70, 0x65, 0x6e, 0x64]); // "spend"
|
||||||
|
|
||||||
const spendKeyMaterial = await crypto.subtle.deriveBits(
|
// Use HMAC with the master key to derive spend key
|
||||||
{
|
const hmacKey = await crypto.subtle.importKey(
|
||||||
name: 'PBKDF2',
|
'raw',
|
||||||
salt: spendSalt,
|
await crypto.subtle.exportKey('raw', masterKey),
|
||||||
iterations: 1000,
|
{ name: 'HMAC', hash: 'SHA-256' },
|
||||||
hash: 'SHA-256'
|
false,
|
||||||
},
|
['sign']
|
||||||
masterKey,
|
);
|
||||||
256
|
|
||||||
|
const spendKeyMaterial = await crypto.subtle.sign(
|
||||||
|
'HMAC',
|
||||||
|
hmacKey,
|
||||||
|
spendSalt
|
||||||
);
|
);
|
||||||
|
|
||||||
return Array.from(new Uint8Array(spendKeyMaterial))
|
return Array.from(new Uint8Array(spendKeyMaterial))
|
||||||
@ -294,15 +325,19 @@ export class SecureCredentialsService {
|
|||||||
private async deriveScanKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> {
|
private async deriveScanKey(masterKey: CryptoKey, salt: Uint8Array): Promise<string> {
|
||||||
const scanSalt = new Uint8Array([...salt, 0x73, 0x63, 0x61, 0x6e]); // "scan"
|
const scanSalt = new Uint8Array([...salt, 0x73, 0x63, 0x61, 0x6e]); // "scan"
|
||||||
|
|
||||||
const scanKeyMaterial = await crypto.subtle.deriveBits(
|
// Use HMAC with the master key to derive scan key
|
||||||
{
|
const hmacKey = await crypto.subtle.importKey(
|
||||||
name: 'PBKDF2',
|
'raw',
|
||||||
salt: scanSalt,
|
await crypto.subtle.exportKey('raw', masterKey),
|
||||||
iterations: 1000,
|
{ name: 'HMAC', hash: 'SHA-256' },
|
||||||
hash: 'SHA-256'
|
false,
|
||||||
},
|
['sign']
|
||||||
masterKey,
|
);
|
||||||
256
|
|
||||||
|
const scanKeyMaterial = await crypto.subtle.sign(
|
||||||
|
'HMAC',
|
||||||
|
hmacKey,
|
||||||
|
scanSalt
|
||||||
);
|
);
|
||||||
|
|
||||||
return Array.from(new Uint8Array(scanKeyMaterial))
|
return Array.from(new Uint8Array(scanKeyMaterial))
|
||||||
|
|||||||
@ -195,6 +195,20 @@ export default class Services {
|
|||||||
// Nettoyer les caches périodiquement
|
// Nettoyer les caches périodiquement
|
||||||
this.startCacheCleanup();
|
this.startCacheCleanup();
|
||||||
|
|
||||||
|
// Initialiser le service PBKDF2 pour les credentials sécurisés
|
||||||
|
try {
|
||||||
|
const { secureCredentialsService } = await import('./secure-credentials.service');
|
||||||
|
secureLogger.info('PBKDF2 service initialized for secure credentials', {
|
||||||
|
component: 'Services',
|
||||||
|
operation: 'pbkdf2_init'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
secureLogger.warn('Failed to initialize PBKDF2 service', error as Error, {
|
||||||
|
component: 'Services',
|
||||||
|
operation: 'pbkdf2_init'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
secureLogger.info('Services initialized', {
|
secureLogger.info('Services initialized', {
|
||||||
component: 'Services',
|
component: 'Services',
|
||||||
operation: 'initialization'
|
operation: 'initialization'
|
||||||
@ -686,6 +700,10 @@ export default class Services {
|
|||||||
|
|
||||||
if (!relayAddress) {
|
if (!relayAddress) {
|
||||||
console.log('⏳ Waiting for relays to be ready...');
|
console.log('⏳ Waiting for relays to be ready...');
|
||||||
|
// Update UI status
|
||||||
|
const { updateCreatorStatus } = await import('../utils/sp-address.utils');
|
||||||
|
updateCreatorStatus('⏳ Waiting for relays to be ready...');
|
||||||
|
|
||||||
await this.getRelayReadyPromise();
|
await this.getRelayReadyPromise();
|
||||||
relayAddress = this.getAllRelays()[0]?.spAddress;
|
relayAddress = this.getAllRelays()[0]?.spAddress;
|
||||||
}
|
}
|
||||||
@ -1656,6 +1674,10 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getProcess(processId: string): Promise<Process | null> {
|
public async getProcess(processId: string): Promise<Process | null> {
|
||||||
|
if (!processId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.processesCache[processId]) {
|
if (this.processesCache[processId]) {
|
||||||
return this.processesCache[processId];
|
return this.processesCache[processId];
|
||||||
} else {
|
} else {
|
||||||
@ -2267,10 +2289,16 @@ export default class Services {
|
|||||||
console.log('Requesting data from peers');
|
console.log('Requesting data from peers');
|
||||||
const membersList = this.getAllMembers();
|
const membersList = this.getAllMembers();
|
||||||
try {
|
try {
|
||||||
const res = this.sdkClient.request_data(processId, stateIds, roles, membersList);
|
// Convert objects to strings for WASM compatibility
|
||||||
|
const rolesString = JSON.stringify(roles);
|
||||||
|
const membersString = JSON.stringify(membersList);
|
||||||
|
const stateIdsString = JSON.stringify(stateIds);
|
||||||
|
|
||||||
|
const res = this.sdkClient.request_data(processId, stateIdsString, rolesString, membersString);
|
||||||
await this.handleApiReturn(res);
|
await this.handleApiReturn(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error('Error requesting data from peers:', e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2444,7 +2444,7 @@ function handleWordsInput() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update creator status
|
// Update creator status
|
||||||
function updateCreatorStatus(message: string, isError: boolean = false) {
|
export function updateCreatorStatus(message: string, isError: boolean = false) {
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
const statusElement = container.querySelector('#creator-status');
|
const statusElement = container.querySelector('#creator-status');
|
||||||
if (statusElement) {
|
if (statusElement) {
|
||||||
@ -2697,6 +2697,34 @@ export async function prepareAndSendPairingTx(): Promise<void> {
|
|||||||
|
|
||||||
console.log(`🔍 DEBUG: Creator address: ${creatorAddress}`);
|
console.log(`🔍 DEBUG: Creator address: ${creatorAddress}`);
|
||||||
|
|
||||||
|
// Update UI with creator address
|
||||||
|
updateCreatorStatus(`Creator address: ${creatorAddress}`);
|
||||||
|
|
||||||
|
// Initialize secure credentials with PBKDF2 and browser credentials
|
||||||
|
try {
|
||||||
|
const { secureCredentialsService } = await import('../services/secure-credentials.service');
|
||||||
|
|
||||||
|
// Check if we're in a secure context (HTTPS)
|
||||||
|
if (window.isSecureContext) {
|
||||||
|
updateCreatorStatus('🔐 Initializing secure credentials with browser...');
|
||||||
|
} else {
|
||||||
|
updateCreatorStatus('🔐 Initializing secure credentials (HTTP mode - WebAuthn not available)...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will trigger the browser popup for WebAuthn (only in HTTPS)
|
||||||
|
const credentials = await secureCredentialsService.generateSecureCredentials('4nk-pairing-password');
|
||||||
|
console.log('✅ Secure credentials initialized with PBKDF2 and WebAuthn');
|
||||||
|
|
||||||
|
if (window.isSecureContext) {
|
||||||
|
updateCreatorStatus('✅ Secure credentials ready (WebAuthn enabled)');
|
||||||
|
} else {
|
||||||
|
updateCreatorStatus('✅ Secure credentials ready (fallback mode - use HTTPS for WebAuthn)');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Secure credentials initialization failed:', error);
|
||||||
|
updateCreatorStatus('⚠️ Using fallback credentials');
|
||||||
|
}
|
||||||
|
|
||||||
// Create pairing process with creator's address
|
// Create pairing process with creator's address
|
||||||
const createPairingProcessReturn = await service.createPairingProcess(
|
const createPairingProcessReturn = await service.createPairingProcess(
|
||||||
creatorAddress, // Use creator's address as memberPublicName
|
creatorAddress, // Use creator's address as memberPublicName
|
||||||
|
|||||||
@ -34,7 +34,8 @@ export async function initWebsocket(url: string) {
|
|||||||
secureLogger.warn('Invalid WebSocket message received', {
|
secureLogger.warn('Invalid WebSocket message received', {
|
||||||
component: 'WebSocket',
|
component: 'WebSocket',
|
||||||
operation: 'message_validation',
|
operation: 'message_validation',
|
||||||
errors: validation.errors
|
errors: validation.errors,
|
||||||
|
messagePreview: msgData.substring(0, 100) // Log first 100 chars for debugging
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import path from 'path'
|
import path from 'path';
|
||||||
|
import wasm from 'vite-plugin-wasm';
|
||||||
|
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['qrcode']
|
include: ['qrcode']
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [
|
||||||
|
wasm(),
|
||||||
|
topLevelAwait()
|
||||||
|
],
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user