fix: None auth and Host.tsx edit button issues
This commit is contained in:
@@ -350,7 +350,40 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
}
|
||||
config.password = resolvedCredentials.password;
|
||||
} else if (resolvedCredentials.authType === "none") {
|
||||
// Don't set password in config - rely on keyboard-interactive
|
||||
// Use authHandler to control authentication flow
|
||||
// This ensures we only try keyboard-interactive, not password auth
|
||||
config.authHandler = (
|
||||
methodsLeft: string[],
|
||||
partialSuccess: boolean,
|
||||
callback: (nextMethod: string | false) => void,
|
||||
) => {
|
||||
fileLogger.info("Auth handler called", {
|
||||
operation: "ssh_auth_handler",
|
||||
hostId,
|
||||
sessionId,
|
||||
methodsLeft,
|
||||
partialSuccess,
|
||||
});
|
||||
|
||||
// Only try keyboard-interactive
|
||||
if (methodsLeft.includes("keyboard-interactive")) {
|
||||
callback("keyboard-interactive");
|
||||
} else {
|
||||
fileLogger.error("Server does not support keyboard-interactive auth", {
|
||||
operation: "ssh_auth_handler_no_keyboard",
|
||||
hostId,
|
||||
sessionId,
|
||||
methodsLeft,
|
||||
});
|
||||
callback(false); // No more methods to try
|
||||
}
|
||||
};
|
||||
|
||||
fileLogger.info("Using keyboard-interactive auth (authType: none)", {
|
||||
operation: "ssh_auth_config",
|
||||
hostId,
|
||||
sessionId,
|
||||
});
|
||||
} else {
|
||||
fileLogger.warn(
|
||||
"No valid authentication method provided for file manager",
|
||||
@@ -531,30 +564,38 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
// If no stored password (including authType "none"), prompt the user
|
||||
if (!hasStoredPassword && passwordPromptIndex !== -1) {
|
||||
if (responseSent) {
|
||||
const responses = prompts.map((p) => {
|
||||
if (/password/i.test(p.prompt) && resolvedCredentials.password) {
|
||||
return resolvedCredentials.password;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
finish(responses);
|
||||
// Connection is already being handled, don't send duplicate responses
|
||||
fileLogger.info(
|
||||
"Skipping duplicate password prompt - response already sent",
|
||||
{
|
||||
operation: "keyboard_interactive_skip",
|
||||
hostId,
|
||||
sessionId,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
responseSent = true;
|
||||
|
||||
if (pendingTOTPSessions[sessionId]) {
|
||||
const responses = prompts.map((p) => {
|
||||
if (/password/i.test(p.prompt) && resolvedCredentials.password) {
|
||||
return resolvedCredentials.password;
|
||||
}
|
||||
return "";
|
||||
// Session already waiting for TOTP, don't override
|
||||
fileLogger.info("Skipping password prompt - TOTP session pending", {
|
||||
operation: "keyboard_interactive_skip",
|
||||
hostId,
|
||||
sessionId,
|
||||
});
|
||||
finish(responses);
|
||||
return;
|
||||
}
|
||||
|
||||
keyboardInteractiveResponded = true;
|
||||
|
||||
fileLogger.info("Requesting password from user (authType: none)", {
|
||||
operation: "keyboard_interactive_password",
|
||||
hostId,
|
||||
sessionId,
|
||||
prompt: prompts[passwordPromptIndex].prompt,
|
||||
});
|
||||
|
||||
pendingTOTPSessions[sessionId] = {
|
||||
client,
|
||||
finish,
|
||||
|
||||
@@ -518,9 +518,6 @@ class PollingManager {
|
||||
};
|
||||
this.statusStore.set(host.id, statusEntry);
|
||||
} catch (error) {
|
||||
statsLogger.warn(
|
||||
`Failed to poll status for host ${host.id}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
const statusEntry: StatusEntry = {
|
||||
status: "offline",
|
||||
lastChecked: new Date().toISOString(),
|
||||
@@ -1088,10 +1085,6 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
const coresNum = Number((coresOut.stdout || "").trim());
|
||||
cores = Number.isFinite(coresNum) && coresNum > 0 ? coresNum : null;
|
||||
} catch (e) {
|
||||
statsLogger.warn(
|
||||
`Failed to collect CPU metrics for host ${host.id}`,
|
||||
e,
|
||||
);
|
||||
cpuPercent = null;
|
||||
cores = null;
|
||||
loadTriplet = null;
|
||||
@@ -1118,10 +1111,6 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
totalGiB = kibToGiB(totalKb);
|
||||
}
|
||||
} catch (e) {
|
||||
statsLogger.warn(
|
||||
`Failed to collect memory metrics for host ${host.id}`,
|
||||
e,
|
||||
);
|
||||
memPercent = null;
|
||||
usedGiB = null;
|
||||
totalGiB = null;
|
||||
@@ -1171,10 +1160,6 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
statsLogger.warn(
|
||||
`Failed to collect disk metrics for host ${host.id}`,
|
||||
e,
|
||||
);
|
||||
diskPercent = null;
|
||||
usedHuman = null;
|
||||
totalHuman = null;
|
||||
@@ -1238,12 +1223,7 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
txBytes: null,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
statsLogger.warn(
|
||||
`Failed to collect network metrics for host ${host.id}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Collect uptime
|
||||
let uptimeSeconds: number | null = null;
|
||||
@@ -1260,9 +1240,7 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
uptimeFormatted = `${days}d ${hours}h ${minutes}m`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
statsLogger.warn(`Failed to collect uptime for host ${host.id}`, e);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Collect process information
|
||||
let totalProcesses: number | null = null;
|
||||
@@ -1305,12 +1283,7 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
);
|
||||
totalProcesses = Number(procCount.stdout.trim()) - 1;
|
||||
runningProcesses = Number(runningCount.stdout.trim());
|
||||
} catch (e) {
|
||||
statsLogger.warn(
|
||||
`Failed to collect process info for host ${host.id}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Collect system information
|
||||
let hostname: string | null = null;
|
||||
@@ -1327,12 +1300,7 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
hostname = hostnameOut.stdout.trim() || null;
|
||||
kernel = kernelOut.stdout.trim() || null;
|
||||
os = osOut.stdout.trim() || null;
|
||||
} catch (e) {
|
||||
statsLogger.warn(
|
||||
`Failed to collect system info for host ${host.id}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
const result = {
|
||||
cpu: { percent: toFixedNum(cpuPercent, 0), cores, load: loadTriplet },
|
||||
|
||||
@@ -793,11 +793,26 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
||||
|
||||
// If no stored password (including authType "none"), prompt the user
|
||||
if (!hasStoredPassword && passwordPromptIndex !== -1) {
|
||||
if (keyboardInteractiveResponded) {
|
||||
// Don't block duplicate password prompts - some servers (like Warpgate) may ask multiple times
|
||||
if (keyboardInteractiveResponded && totpPromptSent) {
|
||||
// Only block if we already sent a TOTP prompt
|
||||
sshLogger.info(
|
||||
"Skipping duplicate password prompt after TOTP sent",
|
||||
{
|
||||
operation: "keyboard_interactive_skip",
|
||||
hostId: id,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
keyboardInteractiveResponded = true;
|
||||
|
||||
sshLogger.info("Requesting password from user (authType: none)", {
|
||||
operation: "keyboard_interactive_password",
|
||||
hostId: id,
|
||||
prompt: prompts[passwordPromptIndex].prompt,
|
||||
});
|
||||
|
||||
keyboardInteractiveFinish = (userResponses: string[]) => {
|
||||
const userInput = (userResponses[0] || "").trim();
|
||||
|
||||
@@ -916,7 +931,37 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
||||
};
|
||||
|
||||
if (resolvedCredentials.authType === "none") {
|
||||
// Don't set password in config - rely on keyboard-interactive
|
||||
// Use authHandler to control authentication flow
|
||||
// This ensures we only try keyboard-interactive, not password auth
|
||||
connectConfig.authHandler = (
|
||||
methodsLeft: string[],
|
||||
partialSuccess: boolean,
|
||||
callback: (nextMethod: string | false) => void,
|
||||
) => {
|
||||
sshLogger.info("Auth handler called", {
|
||||
operation: "ssh_auth_handler",
|
||||
hostId: id,
|
||||
methodsLeft,
|
||||
partialSuccess,
|
||||
});
|
||||
|
||||
// Only try keyboard-interactive
|
||||
if (methodsLeft.includes("keyboard-interactive")) {
|
||||
callback("keyboard-interactive");
|
||||
} else {
|
||||
sshLogger.error("Server does not support keyboard-interactive auth", {
|
||||
operation: "ssh_auth_handler_no_keyboard",
|
||||
hostId: id,
|
||||
methodsLeft,
|
||||
});
|
||||
callback(false); // No more methods to try
|
||||
}
|
||||
};
|
||||
|
||||
sshLogger.info("Using keyboard-interactive auth (authType: none)", {
|
||||
operation: "ssh_auth_config",
|
||||
hostId: id,
|
||||
});
|
||||
} else if (resolvedCredentials.authType === "password") {
|
||||
if (!resolvedCredentials.password) {
|
||||
sshLogger.error(
|
||||
|
||||
Reference in New Issue
Block a user