Files
Netcatty/electron/bridges/keyboardInteractiveHandler.cjs
T
bincxz fea1ebf274 Fix: Auto-answer password prompts in keyboard-interactive authentication
When a server uses keyboard-interactive for both password and 2FA:
1. First round prompts for "Password:" - now auto-filled with configured password
2. Second round prompts for 2FA code - shows modal for user input

This fixes the issue where users had to manually enter password in the
2FA modal, leading to authentication failures.

Applied to:
- sshBridge.cjs
- sftpBridge.cjs
- portForwardingBridge.cjs
2026-01-20 10:31:57 +08:00

105 lines
3.2 KiB
JavaScript

/**
* Keyboard Interactive Handler - Shared state for keyboard-interactive authentication
* This module provides a centralized storage for keyboard-interactive auth requests
* used by SSH, SFTP, and Port Forwarding bridges.
*/
// Keyboard-interactive authentication pending requests
// Map of requestId -> { finishCallback, webContentsId, sessionId, createdAt, timeoutId }
const keyboardInteractiveRequests = new Map();
// TTL for abandoned requests (5 minutes)
const REQUEST_TTL_MS = 5 * 60 * 1000;
/**
* Generate a unique request ID for keyboard-interactive requests
*/
function generateRequestId(prefix = 'ki') {
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
/**
* Store a keyboard-interactive request with TTL cleanup
*/
function storeRequest(requestId, finishCallback, webContentsId, sessionId) {
// Set up TTL timeout to clean up abandoned requests
const timeoutId = setTimeout(() => {
const pending = keyboardInteractiveRequests.get(requestId);
if (pending) {
console.warn(`[KeyboardInteractive] Request ${requestId} timed out after ${REQUEST_TTL_MS / 1000}s, cleaning up`);
keyboardInteractiveRequests.delete(requestId);
// Call finish with empty responses to abort the authentication
try {
pending.finishCallback([]);
} catch (err) {
console.warn(`[KeyboardInteractive] Failed to call finishCallback for timed out request:`, err.message);
}
}
}, REQUEST_TTL_MS);
keyboardInteractiveRequests.set(requestId, {
finishCallback,
webContentsId,
sessionId,
createdAt: Date.now(),
timeoutId,
});
}
/**
* Handle keyboard-interactive authentication response from renderer
*/
function handleResponse(_event, payload) {
console.log(`[KeyboardInteractive] handleResponse called with payload:`, JSON.stringify(payload));
const { requestId, responses, cancelled } = payload;
const pending = keyboardInteractiveRequests.get(requestId);
console.log(`[KeyboardInteractive] Looking for request ${requestId}, found:`, !!pending);
console.log(`[KeyboardInteractive] Current pending requests:`, Array.from(keyboardInteractiveRequests.keys()));
if (!pending) {
console.warn(`[KeyboardInteractive] No pending request for ${requestId}`);
return { success: false, error: 'Request not found' };
}
// Clear the TTL timeout since we received a response
if (pending.timeoutId) {
clearTimeout(pending.timeoutId);
}
keyboardInteractiveRequests.delete(requestId);
if (cancelled) {
console.log(`[KeyboardInteractive] Auth cancelled for ${requestId}`);
pending.finishCallback([]); // Empty responses to cancel
} else {
console.log(`[KeyboardInteractive] Auth response received for ${requestId}, responses count:`, responses?.length);
pending.finishCallback(responses);
}
return { success: true };
}
/**
* Get the requests map (for debugging/testing)
*/
function getRequests() {
return keyboardInteractiveRequests;
}
/**
* Register IPC handler for keyboard-interactive responses
*/
function registerHandler(ipcMain) {
ipcMain.handle("netcatty:keyboard-interactive:respond", handleResponse);
}
module.exports = {
generateRequestId,
storeRequest,
handleResponse,
getRequests,
registerHandler,
};