fix: Sidebar resize issues and issues with TOTP interfering with password auth

This commit is contained in:
LukeGus
2025-11-02 15:44:25 -06:00
parent 9a697a7c10
commit 855a2b5a64
19 changed files with 338 additions and 218 deletions

View File

@@ -173,6 +173,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
authType,
credentialId,
userProvidedPassword,
forceKeyboardInteractive,
} = req.body;
const userId = (req as AuthenticatedRequest).userId;
@@ -257,39 +258,66 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
const config: Record<string, unknown> = {
host: ip,
port: port || 22,
port,
username,
tryKeyboard: true,
readyTimeout: 60000,
keepaliveInterval: 30000,
keepaliveCountMax: 3,
readyTimeout: 60000,
tcpKeepAlive: true,
tcpKeepAliveInitialDelay: 30000,
env: {
TERM: "xterm-256color",
LANG: "en_US.UTF-8",
LC_ALL: "en_US.UTF-8",
LC_CTYPE: "en_US.UTF-8",
LC_MESSAGES: "en_US.UTF-8",
LC_MONETARY: "en_US.UTF-8",
LC_NUMERIC: "en_US.UTF-8",
LC_TIME: "en_US.UTF-8",
LC_COLLATE: "en_US.UTF-8",
COLORTERM: "truecolor",
},
algorithms: {
kex: [
"curve25519-sha256",
"curve25519-sha256@libssh.org",
"ecdh-sha2-nistp521",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp256",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group14-sha256",
"diffie-hellman-group14-sha1",
"diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group-exchange-sha1",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"diffie-hellman-group1-sha1",
],
serverHostKey: [
"ssh-ed25519",
"ecdsa-sha2-nistp521",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp256",
"rsa-sha2-512",
"rsa-sha2-256",
"ssh-rsa",
"ssh-dss",
],
cipher: [
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"aes128-gcm@openssh.com",
"chacha20-poly1305@openssh.com",
"aes256-gcm@openssh.com",
"aes128-cbc",
"aes192-cbc",
"aes128-gcm@openssh.com",
"aes256-ctr",
"aes192-ctr",
"aes128-ctr",
"aes256-cbc",
"aes192-cbc",
"aes128-cbc",
"3des-cbc",
],
hmac: [
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512-etm@openssh.com",
"hmac-sha2-256",
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512",
"hmac-sha2-256",
"hmac-sha1",
"hmac-md5",
],
@@ -335,7 +363,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
.json({ error: "Password required for password authentication" });
}
if (userProvidedPassword) {
if (!forceKeyboardInteractive) {
config.password = resolvedCredentials.password;
}
} else if (resolvedCredentials.authType === "none") {
@@ -413,27 +441,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
});
client.on("error", (err) => {
if (
(err.message.includes("All configured authentication methods failed") ||
err.message.includes("No authentication methods remaining")) &&
resolvedCredentials.authType === "password" &&
!config.password &&
resolvedCredentials.password &&
!userProvidedPassword
) {
fileLogger.info(
"Retrying password auth with password method for file manager",
{
operation: "file_connect_retry",
sessionId,
hostId,
},
);
config.password = resolvedCredentials.password;
client.connect(config);
return;
}
if (responseSent) return;
responseSent = true;
fileLogger.error("SSH connection failed for file manager", {
@@ -613,7 +620,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
return "";
});
keyboardInteractiveResponded = true;
finish(responses);
}
},

View File

@@ -788,10 +788,26 @@ function addLegacyCredentials(
function buildSshConfig(host: SSHHostWithCredentials): ConnectConfig {
const base: ConnectConfig = {
host: host.ip,
port: host.port || 22,
username: host.username || "root",
port: host.port,
username: host.username,
tryKeyboard: true,
readyTimeout: 10_000,
keepaliveInterval: 30000,
keepaliveCountMax: 3,
readyTimeout: 60000,
tcpKeepAlive: true,
tcpKeepAliveInitialDelay: 30000,
env: {
TERM: "xterm-256color",
LANG: "en_US.UTF-8",
LC_ALL: "en_US.UTF-8",
LC_CTYPE: "en_US.UTF-8",
LC_MESSAGES: "en_US.UTF-8",
LC_MONETARY: "en_US.UTF-8",
LC_NUMERIC: "en_US.UTF-8",
LC_TIME: "en_US.UTF-8",
LC_COLLATE: "en_US.UTF-8",
COLORTERM: "truecolor",
},
algorithms: {
kex: [
"curve25519-sha256",

View File

@@ -30,6 +30,7 @@ interface ConnectToHostData {
authType?: string;
credentialId?: number;
userId?: string;
forceKeyboardInteractive?: boolean;
};
initialPath?: string;
executeCommand?: string;
@@ -149,6 +150,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
let pingInterval: NodeJS.Timeout | null = null;
let keyboardInteractiveFinish: ((responses: string[]) => void) | null = null;
let totpPromptSent = false;
let isKeyboardInteractive = false;
let keyboardInteractiveResponded = false;
ws.on("close", () => {
@@ -362,10 +364,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
}
});
async function handleConnectToHost(
data: ConnectToHostData,
retryWithPassword = false,
) {
async function handleConnectToHost(data: ConnectToHostData) {
const { hostConfig, initialPath, executeCommand } = data;
const {
id,
@@ -661,22 +660,6 @@ wss.on("connection", async (ws: WebSocket, req) => {
sshConn.on("error", (err: Error) => {
clearTimeout(connectionTimeout);
if (
(err.message.includes("All configured authentication methods failed") ||
err.message.includes("No authentication methods remaining")) &&
resolvedCredentials.authType === "password" &&
!retryWithPassword &&
!(hostConfig as any).userProvidedPassword
) {
sshLogger.info("Retrying password auth with password method", {
operation: "ssh_connect_retry",
hostId: id,
});
cleanupSSH();
handleConnectToHost(data, true);
return;
}
if (
(authMethodNotAvailable && resolvedCredentials.authType === "none") ||
(resolvedCredentials.authType === "none" &&
@@ -756,6 +739,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
prompts: Array<{ prompt: string; echo: boolean }>,
finish: (responses: string[]) => void,
) => {
isKeyboardInteractive = true;
const promptTexts = prompts.map((p) => p.prompt);
const totpPromptIndex = prompts.findIndex((p) =>
/verification code|verification_code|token|otp|2fa|authenticator|google.*auth/i.test(
@@ -840,7 +824,6 @@ wss.on("connection", async (ws: WebSocket, req) => {
return "";
});
keyboardInteractiveResponded = true;
finish(responses);
}
},
@@ -931,7 +914,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
return;
}
if ((hostConfig as any).userProvidedPassword || retryWithPassword) {
if (!hostConfig.forceKeyboardInteractive) {
connectConfig.password = resolvedCredentials.password;
}
} else if (
@@ -1033,6 +1016,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
}
totpPromptSent = false;
isKeyboardInteractive = false;
keyboardInteractiveResponded = false;
keyboardInteractiveFinish = null;
}

View File

@@ -895,11 +895,24 @@ async function connectSSHTunnel(
host: tunnelConfig.sourceIP,
port: tunnelConfig.sourceSSHPort,
username: tunnelConfig.sourceUsername,
tryKeyboard: true,
keepaliveInterval: 30000,
keepaliveCountMax: 3,
readyTimeout: 60000,
tcpKeepAlive: true,
tcpKeepAliveInitialDelay: 15000,
tcpKeepAliveInitialDelay: 30000,
env: {
TERM: "xterm-256color",
LANG: "en_US.UTF-8",
LC_ALL: "en_US.UTF-8",
LC_CTYPE: "en_US.UTF-8",
LC_MESSAGES: "en_US.UTF-8",
LC_MONETARY: "en_US.UTF-8",
LC_NUMERIC: "en_US.UTF-8",
LC_TIME: "en_US.UTF-8",
LC_COLLATE: "en_US.UTF-8",
COLORTERM: "truecolor",
},
algorithms: {
kex: [
"curve25519-sha256",