fix: Work more on TOTP, renamed homepage to dashboard and began improvements
This commit is contained in:
@@ -290,12 +290,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
};
|
||||
|
||||
if (
|
||||
resolvedCredentials.authType === "password" &&
|
||||
resolvedCredentials.password &&
|
||||
resolvedCredentials.password.trim()
|
||||
) {
|
||||
config.password = resolvedCredentials.password;
|
||||
} else if (
|
||||
resolvedCredentials.authType === "key" &&
|
||||
resolvedCredentials.sshKey &&
|
||||
resolvedCredentials.sshKey.trim()
|
||||
@@ -326,6 +320,22 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
});
|
||||
return res.status(400).json({ error: "Invalid SSH key format" });
|
||||
}
|
||||
} else if (resolvedCredentials.authType === "password") {
|
||||
if (!resolvedCredentials.password || !resolvedCredentials.password.trim()) {
|
||||
fileLogger.warn(
|
||||
"Password authentication requested but no password provided",
|
||||
{
|
||||
operation: "file_connect",
|
||||
sessionId,
|
||||
hostId,
|
||||
},
|
||||
);
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Password required for password authentication" });
|
||||
}
|
||||
// Set password to offer both password and keyboard-interactive methods
|
||||
config.password = resolvedCredentials.password;
|
||||
} else {
|
||||
fileLogger.warn(
|
||||
"No valid authentication method provided for file manager",
|
||||
@@ -403,6 +413,22 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
if (responseSent) return;
|
||||
responseSent = true;
|
||||
|
||||
if (pendingTOTPSessions[sessionId]) {
|
||||
fileLogger.warn(
|
||||
"TOTP session already exists, cleaning up old client",
|
||||
{
|
||||
operation: "file_keyboard_interactive",
|
||||
hostId,
|
||||
sessionId,
|
||||
},
|
||||
);
|
||||
try {
|
||||
pendingTOTPSessions[sessionId].client.end();
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
pendingTOTPSessions[sessionId] = {
|
||||
client,
|
||||
finish,
|
||||
@@ -411,6 +437,13 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
sessionId,
|
||||
};
|
||||
|
||||
fileLogger.info("Created TOTP session", {
|
||||
operation: "file_keyboard_interactive_totp",
|
||||
hostId,
|
||||
sessionId,
|
||||
prompt: totpPrompt.prompt,
|
||||
});
|
||||
|
||||
res.json({
|
||||
requires_totp: true,
|
||||
sessionId,
|
||||
@@ -456,25 +489,38 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
|
||||
operation: "file_totp_verify",
|
||||
sessionId,
|
||||
userId,
|
||||
availableSessions: Object.keys(pendingTOTPSessions),
|
||||
});
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "TOTP session expired. Please reconnect." });
|
||||
}
|
||||
|
||||
delete pendingTOTPSessions[sessionId];
|
||||
|
||||
if (Date.now() - session.createdAt > 120000) {
|
||||
if (Date.now() - session.createdAt > 180000) {
|
||||
delete pendingTOTPSessions[sessionId];
|
||||
try {
|
||||
session.client.end();
|
||||
} catch {
|
||||
// Ignore errors when closing timed out session
|
||||
}
|
||||
fileLogger.warn("TOTP session timeout before code submission", {
|
||||
operation: "file_totp_verify",
|
||||
sessionId,
|
||||
userId,
|
||||
age: Date.now() - session.createdAt,
|
||||
});
|
||||
return res
|
||||
.status(408)
|
||||
.json({ error: "TOTP session timeout. Please reconnect." });
|
||||
}
|
||||
|
||||
fileLogger.info("Submitting TOTP code to SSH server", {
|
||||
operation: "file_totp_verify",
|
||||
sessionId,
|
||||
userId,
|
||||
codeLength: totpCode.length,
|
||||
});
|
||||
|
||||
session.finish([totpCode]);
|
||||
|
||||
let responseSent = false;
|
||||
@@ -483,6 +529,8 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
|
||||
if (responseSent) return;
|
||||
responseSent = true;
|
||||
|
||||
delete pendingTOTPSessions[sessionId];
|
||||
|
||||
sshSessions[sessionId] = {
|
||||
client: session.client,
|
||||
isConnected: true,
|
||||
@@ -506,6 +554,8 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
|
||||
if (responseSent) return;
|
||||
responseSent = true;
|
||||
|
||||
delete pendingTOTPSessions[sessionId];
|
||||
|
||||
fileLogger.error("TOTP verification failed", {
|
||||
operation: "file_totp_verify",
|
||||
sessionId,
|
||||
@@ -519,6 +569,7 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
|
||||
setTimeout(() => {
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
delete pendingTOTPSessions[sessionId];
|
||||
res.status(408).json({ error: "TOTP verification timeout" });
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
@@ -577,7 +577,7 @@ function buildSshConfig(host: SSHHostWithCredentials): ConnectConfig {
|
||||
if (!host.password) {
|
||||
throw new Error(`No password available for host ${host.ip}`);
|
||||
}
|
||||
(base as Record<string, unknown>).password = host.password;
|
||||
// Don't set password in config - let keyboard-interactive handle it
|
||||
} else if (host.authType === "key") {
|
||||
if (!host.key) {
|
||||
throw new Error(`No SSH key available for host ${host.ip}`);
|
||||
|
||||
@@ -672,7 +672,25 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
||||
totpCode,
|
||||
]);
|
||||
|
||||
finish([totpCode]);
|
||||
// Respond to ALL prompts, not just TOTP
|
||||
const responses = prompts.map((p, index) => {
|
||||
if (index === totpPromptIndex) {
|
||||
return totpCode;
|
||||
}
|
||||
if (/password/i.test(p.prompt) && resolvedCredentials.password) {
|
||||
return resolvedCredentials.password;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
sshLogger.info("Full keyboard-interactive response", {
|
||||
operation: "totp_full_response",
|
||||
hostId: id,
|
||||
totalPrompts: prompts.length,
|
||||
responsesProvided: responses.filter((r) => r !== "").length,
|
||||
});
|
||||
|
||||
finish(responses);
|
||||
};
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
@@ -768,15 +786,8 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
||||
compress: ["none", "zlib@openssh.com", "zlib"],
|
||||
},
|
||||
};
|
||||
if (
|
||||
resolvedCredentials.authType === "password" &&
|
||||
resolvedCredentials.password
|
||||
) {
|
||||
connectConfig.password = resolvedCredentials.password;
|
||||
} else if (
|
||||
resolvedCredentials.authType === "key" &&
|
||||
resolvedCredentials.key
|
||||
) {
|
||||
|
||||
if (resolvedCredentials.authType === "key" && resolvedCredentials.key) {
|
||||
try {
|
||||
if (
|
||||
!resolvedCredentials.key.includes("-----BEGIN") ||
|
||||
@@ -814,6 +825,22 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
||||
}),
|
||||
);
|
||||
return;
|
||||
} else if (resolvedCredentials.authType === "password") {
|
||||
if (!resolvedCredentials.password) {
|
||||
sshLogger.error(
|
||||
"Password authentication requested but no password provided",
|
||||
);
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
message:
|
||||
"Password authentication requested but no password provided",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Set password to offer both password and keyboard-interactive methods
|
||||
connectConfig.password = resolvedCredentials.password;
|
||||
} else {
|
||||
sshLogger.error("No valid authentication method provided");
|
||||
ws.send(
|
||||
|
||||
Reference in New Issue
Block a user