fix: implement proper WebAuthn user interaction and fix WebAssembly serialization

**Motivations :**
- WebAuthn requires user gesture (click) to work properly
- Fix WebAssembly serialization error 'invalid type: sequence, expected a map'
- Provide clear UI for user to trigger WebAuthn authentication
- Ensure proper error handling for authentication failures

**Modifications :**
- Added authentication button in home.ts that requires user click for WebAuthn
- Fixed WebAssembly members parameter to pass object map instead of array
- Added CSS styles for authentication button with hover effects
- Improved error handling and user feedback for authentication process
- Maintained user interaction requirement for WebAuthn security

**Pages affectées :**
- src/pages/home/home.ts: Added user interaction button for WebAuthn
- src/services/service.ts: Fixed WebAssembly serialization to use object map
- src/4nk.css: Added authentication button styles with responsive design
This commit is contained in:
NicolasCantu 2025-10-23 21:59:35 +02:00
parent 5def07797e
commit 4f8e43ed87
4 changed files with 114 additions and 43 deletions

View File

@ -1230,6 +1230,45 @@ select[data-multi-select-plugin] {
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
}
/* Authentication Button Styles */
.auth-container {
text-align: center;
padding: 20px;
}
.auth-button {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
margin: 20px 0;
min-width: 250px;
}
.auth-button:hover {
background: linear-gradient(135deg, #0056b3, #004085);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);
}
.auth-button:active {
transform: translateY(0);
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
}
.auth-hint {
color: #6c757d;
font-size: 14px;
margin-top: 10px;
font-style: italic;
}
/* Responsive Design for Mode Selection */
@media (max-width: 768px) {
.mode-buttons {
@ -1244,4 +1283,10 @@ select[data-multi-select-plugin] {
position: static;
margin-top: 10px;
}
.auth-button {
min-width: 200px;
padding: 14px 28px;
font-size: 14px;
}
}

View File

@ -547,51 +547,76 @@ async function handleMainPairing(): Promise<void> {
const mainStatus = container.querySelector('#main-status') as HTMLElement;
try {
// Update UI
// Show authentication button that requires user interaction
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
mainStatus.innerHTML = `
<div class="auth-container">
<p>🔐 Secure authentication required</p>
<button id="authButton" class="auth-button">🔐 Authenticate with Browser</button>
<p class="auth-hint">Click the button above to authenticate with your browser</p>
</div>
`;
}
// Always trigger WebAuthn flow for authentication
console.log('🔐 Triggering WebAuthn authentication...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
}
// Import and trigger WebAuthn directly
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
// Check if we have existing credentials
const hasCredentials = await secureCredentialsService.hasCredentials();
if (hasCredentials) {
console.log('🔓 Existing credentials found, decrypting...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
// Wait for user to click the authentication button
await new Promise<void>((resolve, reject) => {
const authButton = document.getElementById('authButton') as HTMLButtonElement;
if (!authButton) {
reject(new Error('Authentication button not found'));
return;
}
// This will trigger WebAuthn for decryption
await secureCredentialsService.retrieveCredentials('');
authButton.addEventListener('click', async () => {
try {
// Update UI to show authentication in progress
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
}
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
}
} else {
console.log('🔐 No existing credentials, creating new ones...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new credentials...</span>';
}
// Import and trigger WebAuthn directly
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
// This will trigger WebAuthn for creation
await secureCredentialsService.generateSecureCredentials('');
// Check if we have existing credentials
const hasCredentials = await secureCredentialsService.hasCredentials();
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New credentials created successfully</span>';
}
}
if (hasCredentials) {
console.log('🔓 Existing credentials found, decrypting...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
}
// Now proceed with pairing process
await prepareAndSendPairingTx();
// This will trigger WebAuthn for decryption
await secureCredentialsService.retrieveCredentials('');
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
}
} else {
console.log('🔐 No existing credentials, creating new ones...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new credentials...</span>';
}
// This will trigger WebAuthn for creation
await secureCredentialsService.generateSecureCredentials('');
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New credentials created successfully</span>';
}
}
// Now proceed with pairing process
await prepareAndSendPairingTx();
resolve();
} catch (error) {
console.error('Authentication failed:', error);
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Authentication failed. Please try again.</span>';
}
reject(error);
}
});
});
} catch (error) {
console.error('Pairing failed:', error);

View File

@ -903,12 +903,13 @@ export default class Services {
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);
// Convert to map format for WebAssembly (keep original structure)
const members = membersObj;
console.log('🔍 DEBUG: Members map keys:', Object.keys(members));
console.log('🔍 DEBUG: Members map sample:', Object.keys(members).slice(0, 3).reduce((acc, key) => {
acc[key] = members[key];
return acc;
}, {} as any));
const result = this.sdkClient.create_new_process(
encodedPrivateData,