fix: resolve multiple critical issues

**Motivations :**
- Fix WebAuthn authentication regression (button reappeared instead of auto-trigger)
- Resolve infinite loop of 'process.states is not an array' logs
- Fix WebAssembly serialization error 'invalid type: map, expected a sequence'
- Improve WebAuthn error handling and timeout management

**Modifications :**
- Restored automatic WebAuthn triggering in home.ts initHomePage()
- Fixed handshake deduplication logic in service.ts using content-based keys
- Added membersList validation before WebAssembly calls to prevent empty object errors
- Enhanced WebAuthn error handling with specific error messages and increased timeout to 2 minutes
- Improved error messages for NotAllowedError, NotSupportedError, and SecurityError

**Pages affectées :**
- src/pages/home/home.ts: Restored auto WebAuthn trigger, removed manual button
- src/services/service.ts: Fixed handshake deduplication and added membersList validation
- src/services/secure-credentials.service.ts: Enhanced WebAuthn error handling and timeout
This commit is contained in:
NicolasCantu 2025-10-23 21:49:20 +02:00
parent 8af1fd055d
commit e393a4f615
3 changed files with 99 additions and 28 deletions

View File

@ -524,9 +524,21 @@ export function setupIframePairingButtons() {
}
}
// Main Pairing Interface - Auto-triggered, no button needed
// Main Pairing Interface - Automatic WebAuthn trigger
export function setupMainPairing(): void {
// No button setup needed since authentication is automatic
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const mainStatus = container.querySelector('#main-status') as HTMLElement;
if (mainStatus) {
mainStatus.innerHTML = `
<div class="auth-container">
<p>🔐 Secure authentication required</p>
<div class="spinner"></div>
<span>Initializing secure authentication...</span>
</div>
`;
}
console.log('🔐 Main pairing setup - authentication will be automatic');
}

View File

@ -99,16 +99,32 @@ export class SecureCredentialsService {
authenticatorAttachment: "platform", // Force l'authentificateur intégré
userVerification: "required"
},
timeout: 60000,
timeout: 120000, // 2 minutes timeout
attestation: "direct"
};
console.log('🔐 Requesting WebAuthn credential creation for encryption key...');
// Créer le credential WebAuthn
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
}) as PublicKeyCredential;
// Créer le credential WebAuthn avec gestion d'erreur robuste
let credential: PublicKeyCredential;
try {
credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
}) as PublicKeyCredential;
} catch (error) {
if (error instanceof Error) {
if (error.name === 'NotAllowedError') {
throw new Error('WebAuthn authentication was cancelled or timed out. Please try again and complete the authentication when prompted.');
} else if (error.name === 'NotSupportedError') {
throw new Error('WebAuthn is not supported in this browser. Please use a modern browser with WebAuthn support.');
} else if (error.name === 'SecurityError') {
throw new Error('WebAuthn security error. Please ensure you are using HTTPS and try again.');
} else {
throw new Error(`WebAuthn error: ${error.message}`);
}
}
throw error;
}
if (!credential) {
throw new Error('WebAuthn credential creation failed');
@ -283,18 +299,34 @@ export class SecureCredentialsService {
throw new Error('WebAuthn not supported for decryption');
}
// Demander l'authentification WebAuthn
const credential = await navigator.credentials.get({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)),
allowCredentials: [{
id: new TextEncoder().encode(credentialId),
type: 'public-key'
}],
userVerification: 'required',
timeout: 60000
// Demander l'authentification WebAuthn avec gestion d'erreur robuste
let credential: PublicKeyCredential;
try {
credential = await navigator.credentials.get({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)),
allowCredentials: [{
id: new TextEncoder().encode(credentialId),
type: 'public-key'
}],
userVerification: 'required',
timeout: 120000 // 2 minutes timeout
}
}) as PublicKeyCredential;
} catch (error) {
if (error instanceof Error) {
if (error.name === 'NotAllowedError') {
throw new Error('WebAuthn authentication was cancelled or timed out. Please try again and complete the authentication when prompted.');
} else if (error.name === 'NotSupportedError') {
throw new Error('WebAuthn is not supported in this browser. Please use a modern browser with WebAuthn support.');
} else if (error.name === 'SecurityError') {
throw new Error('WebAuthn security error. Please ensure you are using HTTPS and try again.');
} else {
throw new Error(`WebAuthn decryption error: ${error.message}`);
}
}
}) as PublicKeyCredential;
throw error;
}
if (!credential) {
throw new Error('WebAuthn authentication failed');

View File

@ -897,9 +897,18 @@ export default class Services {
console.log('🔍 DEBUG: Members type:', typeof membersObj);
console.log('🔍 DEBUG: Members keys:', Object.keys(membersObj));
// Convert object to array for WebAssembly
const members = Object.values(membersObj);
// Check if membersList is empty
if (!membersObj || Object.keys(membersObj).length === 0) {
console.warn('⚠️ No members available for create_new_process, waiting for handshake...');
throw new Error('No members available - handshake not completed yet');
}
// Convert to simple array of SP addresses for WebAssembly
const members = Object.values(membersObj).map(member => ({
sp_addresses: member.sp_addresses
}));
console.log('🔍 DEBUG: Members array length:', members.length);
console.log('🔍 DEBUG: Members simplified:', members);
const result = this.sdkClient.create_new_process(
encodedPrivateData,
@ -943,12 +952,15 @@ export default class Services {
...this.sdkClient.encode_binary(publicSplitData.binaryData),
};
try {
const members = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
const result = this.sdkClient.update_process(
process,
encodedPrivateData,
roles,
encodedPublicData,
Object.values(this.getAllMembers())
members
);
if (result.updated_process) {
await this.checkConnections(result.updated_process.current_process);
@ -969,7 +981,10 @@ export default class Services {
await this.checkConnections(process);
}
try {
return this.sdkClient.create_update_message(process, stateId, Object.values(this.getAllMembers()));
const members = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
return this.sdkClient.create_update_message(process, stateId, members);
} catch (e) {
throw new Error(`Failed to create prd update: ${e}`);
}
@ -981,7 +996,10 @@ export default class Services {
throw new Error('Unknown process');
}
try {
return this.sdkClient.create_response_prd(process, stateId, Object.values(this.getAllMembers()));
const members = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
return this.sdkClient.create_response_prd(process, stateId, members);
} catch (e) {
throw new Error(`Failed to create response prd: ${e}`);
}
@ -993,7 +1011,10 @@ export default class Services {
throw new Error('Failed to get process from db');
}
try {
const result = this.sdkClient.validate_state(process, stateId, Object.values(this.getAllMembers()));
const members = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
const result = this.sdkClient.validate_state(process, stateId, members);
if (result.updated_process) {
await this.checkConnections(result.updated_process.current_process);
return result;
@ -1049,7 +1070,9 @@ export default class Services {
}
async parseCipher(message: string) {
const membersList = Object.values(this.getAllMembers());
const membersList = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
const processes = await this.getProcesses();
try {
// console.log('parsing new cipher');
@ -1080,7 +1103,9 @@ export default class Services {
return;
}
const membersList = Object.values(this.getAllMembers());
const membersList = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
try {
// Does the transaction spend the tip of a process?
const prevouts = this.sdkClient.get_prevouts(parsedMsg.transaction);
@ -2178,7 +2203,7 @@ export default class Services {
}
// Add a flag to prevent processing the same handshake multiple times
const handshakeKey = `${url}_${Date.now()}`;
const handshakeKey = `${url}_${JSON.stringify(handshakeMsg.processes_list)}`;
if (this.processedHandshakes && this.processedHandshakes.has(handshakeKey)) {
console.debug('Handshake already processed for', url);
return;
@ -2479,7 +2504,9 @@ export default class Services {
roles: Record<string, RoleDefinition>[]
) {
console.log('Requesting data from peers');
const membersList = Object.values(this.getAllMembers());
const membersList = Object.values(this.getAllMembers()).map(member => ({
sp_addresses: member.sp_addresses
}));
try {
// Convert objects to strings for WASM compatibility
const rolesString = JSON.stringify(roles);