From 63e776f183403befc95ed3551a62181c69166d23 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sun, 28 Sep 2025 21:36:20 -0500 Subject: [PATCH] Code cleanup --- electron/main.cjs | 16 +- src/backend/database/database.ts | 2 +- src/backend/database/routes/credentials.ts | 237 +++----- src/backend/database/routes/users.ts | 8 +- src/backend/ssh/file-manager.ts | 517 +++++++++--------- src/backend/ssh/terminal.ts | 10 +- src/backend/starter.ts | 16 +- src/backend/utils/auto-ssl-setup.ts | 4 +- src/components/ui/sonner.tsx | 76 +-- src/components/ui/version-alert.tsx | 5 +- src/components/ui/version-check-modal.tsx | 29 +- .../Apps/Credentials/CredentialsManager.tsx | 5 +- src/ui/Desktop/Homepage/HomepageAuth.tsx | 13 +- src/ui/Mobile/Homepage/HomepageAuth.tsx | 13 +- src/ui/main-axios.ts | 51 +- 15 files changed, 467 insertions(+), 535 deletions(-) diff --git a/electron/main.cjs b/electron/main.cjs index 92c1af75..7a7ba5bc 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -3,10 +3,10 @@ const path = require("path"); const fs = require("fs"); const os = require("os"); -app.commandLine.appendSwitch('--ignore-certificate-errors'); -app.commandLine.appendSwitch('--ignore-ssl-errors'); -app.commandLine.appendSwitch('--ignore-certificate-errors-spki-list'); -app.commandLine.appendSwitch('--enable-features=NetworkService'); +app.commandLine.appendSwitch("--ignore-certificate-errors"); +app.commandLine.appendSwitch("--ignore-ssl-errors"); +app.commandLine.appendSwitch("--ignore-certificate-errors-spki-list"); +app.commandLine.appendSwitch("--enable-features=NetworkService"); let mainWindow = null; @@ -141,9 +141,9 @@ async function fetchGitHubAPI(endpoint, cacheKey) { requestOptions.rejectUnauthorized = false; requestOptions.agent = new https.Agent({ rejectUnauthorized: false, - secureProtocol: 'TLSv1_2_method', + secureProtocol: "TLSv1_2_method", checkServerIdentity: () => undefined, - ciphers: 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH', + ciphers: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", honorCipherOrder: true, }); } @@ -315,9 +315,9 @@ ipcMain.handle("test-server-connection", async (event, serverUrl) => { requestOptions.rejectUnauthorized = false; requestOptions.agent = new https.Agent({ rejectUnauthorized: false, - secureProtocol: 'TLSv1_2_method', + secureProtocol: "TLSv1_2_method", checkServerIdentity: () => undefined, - ciphers: 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH', + ciphers: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", honorCipherOrder: true, }); } diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index ae559291..b96089f9 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -245,7 +245,7 @@ app.get("/version", authenticateJWT, async (req, res) => { } catch { return null; } - } + }, ]; for (const getVersion of versionSources) { diff --git a/src/backend/database/routes/credentials.ts b/src/backend/database/routes/credentials.ts index b25767fc..fcb0f936 100644 --- a/src/backend/database/routes/credentials.ts +++ b/src/backend/database/routes/credentials.ts @@ -1128,93 +1128,79 @@ async function deploySSHKeyToHost( conn.on("ready", async () => { clearTimeout(connectionTimeout); - authLogger.info("SSH connection established for key deployment", { - host: hostConfig.ip, - username: hostConfig.username, - authType: hostConfig.authType, - }); try { - authLogger.info("Ensuring .ssh directory exists", { host: hostConfig.ip }); await new Promise((resolveCmd, rejectCmd) => { const cmdTimeout = setTimeout(() => { rejectCmd(new Error("mkdir command timeout")); - }, 10000); // Reduced to 10 seconds + }, 10000); - // Use a more robust command that handles existing directories - conn.exec("test -d ~/.ssh || mkdir -p ~/.ssh; chmod 700 ~/.ssh", (err, stream) => { - if (err) { - clearTimeout(cmdTimeout); - authLogger.error("mkdir command error", { host: hostConfig.ip, error: err.message }); - return rejectCmd(err); - } - - stream.on("close", (code) => { - clearTimeout(cmdTimeout); - authLogger.info("mkdir command completed", { host: hostConfig.ip, code }); - if (code === 0) { - resolveCmd(); - } else { - rejectCmd(new Error(`mkdir command failed with code ${code}`)); + conn.exec( + "test -d ~/.ssh || mkdir -p ~/.ssh; chmod 700 ~/.ssh", + (err, stream) => { + if (err) { + clearTimeout(cmdTimeout); + return rejectCmd(err); } - }); - stream.on("data", (data) => { - authLogger.info("mkdir command output", { host: hostConfig.ip, output: data.toString() }); - }); - }); + stream.on("close", (code) => { + clearTimeout(cmdTimeout); + if (code === 0) { + resolveCmd(); + } else { + rejectCmd( + new Error(`mkdir command failed with code ${code}`), + ); + } + }); + + stream.on("data", (data) => {}); + }, + ); }); const keyExists = await new Promise( (resolveCheck, rejectCheck) => { const checkTimeout = setTimeout(() => { rejectCheck(new Error("Key check timeout")); - }, 5000); // Reduced to 5 seconds + }, 5000); - // Parse public key - handle both JSON and plain text formats let actualPublicKey = publicKey; try { - // Try to parse as JSON first const parsed = JSON.parse(publicKey); if (parsed.data) { actualPublicKey = parsed.data; - authLogger.info("Parsed public key from JSON format", { host: hostConfig.ip }); } - } catch (e) { - // Not JSON, use as-is - authLogger.info("Using public key as plain text", { host: hostConfig.ip }); - } + } catch (e) {} - // Validate public key format const keyParts = actualPublicKey.trim().split(" "); if (keyParts.length < 2) { clearTimeout(checkTimeout); - authLogger.error("Invalid public key format", { host: hostConfig.ip, publicKey: actualPublicKey.substring(0, 50) + "..." }); - return rejectCheck(new Error("Invalid public key format - must contain at least 2 parts")); + return rejectCheck( + new Error( + "Invalid public key format - must contain at least 2 parts", + ), + ); } const keyPattern = keyParts[1]; - authLogger.info("Checking for existing key", { host: hostConfig.ip, keyPattern: keyPattern.substring(0, 20) + "..." }); - - // Use a simpler approach - just check if the file exists and has content + conn.exec( `if [ -f ~/.ssh/authorized_keys ]; then grep -F "${keyPattern}" ~/.ssh/authorized_keys >/dev/null 2>&1; echo $?; else echo 1; fi`, (err, stream) => { if (err) { clearTimeout(checkTimeout); - authLogger.error("Key check error", { host: hostConfig.ip, error: err.message }); return rejectCheck(err); } - let output = ''; - stream.on('data', (data) => { + let output = ""; + stream.on("data", (data) => { output += data.toString(); }); stream.on("close", (code) => { clearTimeout(checkTimeout); - const exists = output.trim() === '0'; - authLogger.info("Key check completed", { host: hostConfig.ip, code, output: output.trim(), exists }); + const exists = output.trim() === "0"; resolveCheck(exists); }); }, @@ -1228,40 +1214,33 @@ async function deploySSHKeyToHost( return; } - authLogger.info("Adding SSH key to authorized_keys", { host: hostConfig.ip }); await new Promise((resolveAdd, rejectAdd) => { const addTimeout = setTimeout(() => { rejectAdd(new Error("Key add timeout")); - }, 10000); // Reduced to 10 seconds + }, 10000); - // Parse public key - handle both JSON and plain text formats let actualPublicKey = publicKey; try { - // Try to parse as JSON first const parsed = JSON.parse(publicKey); if (parsed.data) { actualPublicKey = parsed.data; } - } catch (e) { - // Not JSON, use as-is - } + } catch (e) {} + + const escapedKey = actualPublicKey + .replace(/\\/g, "\\\\") + .replace(/'/g, "'\\''"); - // Use printf instead of echo for more reliable key addition - const escapedKey = actualPublicKey.replace(/\\/g, '\\\\').replace(/'/g, "'\\''"); - authLogger.info("Adding key to authorized_keys", { host: hostConfig.ip, keyLength: actualPublicKey.length }); - conn.exec( `printf '%s\\n' '${escapedKey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`, (err, stream) => { if (err) { clearTimeout(addTimeout); - authLogger.error("Key add error", { host: hostConfig.ip, error: err.message }); return rejectAdd(err); } stream.on("close", (code) => { clearTimeout(addTimeout); - authLogger.info("Key add completed", { host: hostConfig.ip, code }); if (code === 0) { resolveAdd(); } else { @@ -1270,39 +1249,32 @@ async function deploySSHKeyToHost( ); } }); - - stream.on("data", (data) => { - authLogger.info("Key add output", { host: hostConfig.ip, output: data.toString() }); - }); }, ); }); - authLogger.info("Verifying key deployment", { host: hostConfig.ip }); const verifySuccess = await new Promise( (resolveVerify, rejectVerify) => { const verifyTimeout = setTimeout(() => { rejectVerify(new Error("Key verification timeout")); - }, 5000); // Reduced to 5 seconds + }, 5000); - // Parse public key - handle both JSON and plain text formats let actualPublicKey = publicKey; try { - // Try to parse as JSON first const parsed = JSON.parse(publicKey); if (parsed.data) { actualPublicKey = parsed.data; } - } catch (e) { - // Not JSON, use as-is - } + } catch (e) {} - // Use the same key pattern extraction as above const keyParts = actualPublicKey.trim().split(" "); if (keyParts.length < 2) { clearTimeout(verifyTimeout); - authLogger.error("Invalid public key format for verification", { host: hostConfig.ip, publicKey: actualPublicKey.substring(0, 50) + "..." }); - return rejectVerify(new Error("Invalid public key format - must contain at least 2 parts")); + return rejectVerify( + new Error( + "Invalid public key format - must contain at least 2 parts", + ), + ); } const keyPattern = keyParts[1]; @@ -1311,19 +1283,17 @@ async function deploySSHKeyToHost( (err, stream) => { if (err) { clearTimeout(verifyTimeout); - authLogger.error("Key verification error", { host: hostConfig.ip, error: err.message }); return rejectVerify(err); } - let output = ''; - stream.on('data', (data) => { + let output = ""; + stream.on("data", (data) => { output += data.toString(); }); stream.on("close", (code) => { clearTimeout(verifyTimeout); - const verified = output.trim() === '0'; - authLogger.info("Key verification completed", { host: hostConfig.ip, code, output: output.trim(), verified }); + const verified = output.trim() === "0"; resolveVerify(verified); }); }, @@ -1353,30 +1323,31 @@ async function deploySSHKeyToHost( conn.on("error", (err) => { clearTimeout(connectionTimeout); let errorMessage = err.message; - - // Log detailed error information for debugging - authLogger.error("SSH connection failed during key deployment", { - host: hostConfig.ip, - username: hostConfig.username, - authType: hostConfig.authType, - hasPassword: !!hostConfig.password, - hasPrivateKey: !!hostConfig.privateKey, - error: err.message, - errorCode: (err as any).code, - }); - - if (err.message.includes("All configured authentication methods failed")) { - errorMessage = "Authentication failed. Please check your credentials and ensure the SSH service is running."; - } else if (err.message.includes("ENOTFOUND") || err.message.includes("ENOENT")) { + + if ( + err.message.includes("All configured authentication methods failed") + ) { + errorMessage = + "Authentication failed. Please check your credentials and ensure the SSH service is running."; + } else if ( + err.message.includes("ENOTFOUND") || + err.message.includes("ENOENT") + ) { errorMessage = "Could not resolve hostname or connect to server."; } else if (err.message.includes("ECONNREFUSED")) { - errorMessage = "Connection refused. The server may not be running or the port may be incorrect."; + errorMessage = + "Connection refused. The server may not be running or the port may be incorrect."; } else if (err.message.includes("ETIMEDOUT")) { - errorMessage = "Connection timed out. Check your network connection and server availability."; - } else if (err.message.includes("authentication failed") || err.message.includes("Permission denied")) { - errorMessage = "Authentication failed. Please check your username and password/key."; + errorMessage = + "Connection timed out. Check your network connection and server availability."; + } else if ( + err.message.includes("authentication failed") || + err.message.includes("Permission denied") + ) { + errorMessage = + "Authentication failed. Please check your username and password/key."; } - + resolve({ success: false, error: errorMessage }); }); @@ -1462,24 +1433,9 @@ async function deploySSHKeyToHost( return; } - // Log connection attempt - authLogger.info("Attempting SSH connection for key deployment", { - host: connectionConfig.host, - port: connectionConfig.port, - username: connectionConfig.username, - authType: hostConfig.authType, - hasPassword: !!connectionConfig.password, - hasPrivateKey: !!connectionConfig.privateKey, - hasPassphrase: !!connectionConfig.passphrase, - }); - conn.connect(connectionConfig); } catch (error) { clearTimeout(connectionTimeout); - authLogger.error("Failed to initiate SSH connection", { - host: hostConfig.ip, - error: error instanceof Error ? error.message : "Unknown error", - }); resolve({ success: false, error: error instanceof Error ? error.message : "Connection failed", @@ -1547,11 +1503,7 @@ router.post( }); } const targetHost = await SimpleDBOps.select( - db - .select() - .from(sshData) - .where(eq(sshData.id, targetHostId)) - .limit(1), + db.select().from(sshData).where(eq(sshData.id, targetHostId)).limit(1), "ssh_data", userId, ); @@ -1575,25 +1527,9 @@ router.post( keyPassword: hostData.keyPassword, }; - authLogger.info("Host configuration for SSH key deployment", { - hostId: targetHostId, - ip: hostConfig.ip, - port: hostConfig.port, - username: hostConfig.username, - authType: hostConfig.authType, - hasPassword: !!hostConfig.password, - hasPrivateKey: !!hostConfig.privateKey, - hasKeyPassword: !!hostConfig.keyPassword, - passwordLength: hostConfig.password ? hostConfig.password.length : 0, - }); - if (hostData.authType === "credential" && hostData.credentialId) { const userId = (req as any).userId; if (!userId) { - authLogger.error("Missing userId for credential resolution", { - hostId: targetHostId, - credentialId: hostData.credentialId, - }); return res.status(400).json({ success: false, error: "Authentication required for credential resolution", @@ -1624,32 +1560,13 @@ router.post( hostConfig.privateKey = cred.privateKey || cred.key; hostConfig.keyPassword = cred.keyPassword; } - - authLogger.info("Resolved host credentials for SSH key deployment", { - hostId: targetHostId, - credentialId: hostData.credentialId, - authType: hostConfig.authType, - username: hostConfig.username, - hasPassword: !!hostConfig.password, - hasPrivateKey: !!hostConfig.privateKey, - hasKeyPassword: !!hostConfig.keyPassword, - }); } else { - authLogger.error("Host credential not found", { - hostId: targetHostId, - credentialId: hostData.credentialId, - }); return res.status(400).json({ success: false, error: "Host credential not found", }); } } catch (error) { - authLogger.error("Failed to resolve host credentials", { - hostId: targetHostId, - credentialId: hostData.credentialId, - error: error instanceof Error ? error.message : "Unknown error", - }); return res.status(500).json({ success: false, error: "Failed to resolve host credentials", @@ -1664,31 +1581,17 @@ router.post( ); if (deployResult.success) { - authLogger.success(`SSH key deployed successfully`, { - credentialId, - targetHostId, - operation: "deploy_ssh_key", - }); - res.json({ success: true, message: deployResult.message || "SSH key deployed successfully", }); } else { - authLogger.error(`SSH key deployment failed`, { - credentialId, - targetHostId, - error: deployResult.error, - operation: "deploy_ssh_key", - }); - res.status(500).json({ success: false, error: deployResult.error || "Deployment failed", }); } } catch (error) { - authLogger.error("Failed to deploy SSH key", error); res.status(500).json({ success: false, error: diff --git a/src/backend/database/routes/users.ts b/src/backend/database/routes/users.ts index cbdfd049..848c0a2f 100644 --- a/src/backend/database/routes/users.ts +++ b/src/backend/database/routes/users.ts @@ -926,8 +926,10 @@ router.post("/login", async (req, res) => { username: userRecord.username, }; - const isElectron = req.headers['x-electron-app'] === 'true' || req.headers['X-Electron-App'] === 'true'; - + const isElectron = + req.headers["x-electron-app"] === "true" || + req.headers["X-Electron-App"] === "true"; + if (isElectron) { response.token = token; } @@ -1507,7 +1509,7 @@ router.post("/totp/verify-login", async (req, res) => { success: true, is_admin: !!userRecord.is_admin, username: userRecord.username, - token: req.headers['x-electron-app'] === 'true' ? token : undefined, + token: req.headers["x-electron-app"] === "true" ? token : undefined, }); } catch (err) { authLogger.error("TOTP verification failed", err); diff --git a/src/backend/ssh/file-manager.ts b/src/backend/ssh/file-manager.ts index 28c75625..4453adb7 100644 --- a/src/backend/ssh/file-manager.ts +++ b/src/backend/ssh/file-manager.ts @@ -9,10 +9,9 @@ import { fileLogger } from "../utils/logger.js"; import { SimpleDBOps } from "../utils/simple-db-ops.js"; import { AuthManager } from "../utils/auth-manager.js"; - function isExecutableFile(permissions: string, fileName: string): boolean { const hasExecutePermission = - permissions[3] === "x" || permissions[6] === "x" || permissions[9] === "x"; + permissions[3] === "x" || permissions[6] === "x" || permissions[9] === "x"; const scriptExtensions = [ ".sh", @@ -26,59 +25,59 @@ function isExecutableFile(permissions: string, fileName: string): boolean { ".fish", ]; const hasScriptExtension = scriptExtensions.some((ext) => - fileName.toLowerCase().endsWith(ext), + fileName.toLowerCase().endsWith(ext), ); const executableExtensions = [".bin", ".exe", ".out"]; const hasExecutableExtension = executableExtensions.some((ext) => - fileName.toLowerCase().endsWith(ext), + fileName.toLowerCase().endsWith(ext), ); const hasNoExtension = !fileName.includes(".") && hasExecutePermission; return ( - hasExecutePermission && - (hasScriptExtension || hasExecutableExtension || hasNoExtension) + hasExecutePermission && + (hasScriptExtension || hasExecutableExtension || hasNoExtension) ); } const app = express(); app.use( - cors({ - origin: (origin, callback) => { - if (!origin) return callback(null, true); + cors({ + origin: (origin, callback) => { + if (!origin) return callback(null, true); - const allowedOrigins = [ - "http://localhost:5173", - "http://localhost:3000", - "http://127.0.0.1:5173", - "http://127.0.0.1:3000", - ]; + const allowedOrigins = [ + "http://localhost:5173", + "http://localhost:3000", + "http://127.0.0.1:5173", + "http://127.0.0.1:3000", + ]; - if (origin.startsWith("https://")) { - return callback(null, true); - } + if (origin.startsWith("https://")) { + return callback(null, true); + } - if (origin.startsWith("http://")) { - return callback(null, true); - } + if (origin.startsWith("http://")) { + return callback(null, true); + } - if (allowedOrigins.includes(origin)) { - return callback(null, true); - } + if (allowedOrigins.includes(origin)) { + return callback(null, true); + } - callback(new Error("Not allowed by CORS")); - }, - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: [ - "Content-Type", - "Authorization", - "User-Agent", - "X-Electron-App", - ], - }), + callback(new Error("Not allowed by CORS")); + }, + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allowedHeaders: [ + "Content-Type", + "Authorization", + "User-Agent", + "X-Electron-App", + ], + }), ); app.use(cookieParser()); app.use(express.json({ limit: "1gb" })); @@ -88,8 +87,6 @@ app.use(express.raw({ limit: "5gb", type: "application/octet-stream" })); const authManager = AuthManager.getInstance(); app.use(authManager.createAuthMiddleware()); - - interface SSHSession { client: SSHClient; isConnected: boolean; @@ -116,10 +113,10 @@ function scheduleSessionCleanup(sessionId: string) { if (session.timeout) clearTimeout(session.timeout); session.timeout = setTimeout( - () => { - cleanupSession(sessionId); - }, - 30 * 60 * 1000, + () => { + cleanupSession(sessionId); + }, + 30 * 60 * 1000, ); } } @@ -144,7 +141,6 @@ function getMimeType(fileName: string): string { return mimeTypes[ext || ""] || "application/octet-stream"; } - app.post("/ssh/file_manager/ssh/connect", async (req, res) => { const { sessionId, @@ -189,17 +185,17 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { if (credentialId && hostId && userId) { try { const credentials = await SimpleDBOps.select( - getDb() - .select() - .from(sshCredentials) - .where( - and( - eq(sshCredentials.id, credentialId), - eq(sshCredentials.userId, userId), - ), - ), - "ssh_credentials", - userId, + getDb() + .select() + .from(sshCredentials) + .where( + and( + eq(sshCredentials.id, credentialId), + eq(sshCredentials.userId, userId), + ), + ), + "ssh_credentials", + userId, ); if (credentials.length > 0) { @@ -228,13 +224,13 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { } } else if (credentialId && hostId) { fileLogger.warn( - "Missing userId for credential resolution in file manager", - { - operation: "ssh_credentials", - hostId, - credentialId, - hasUserId: !!userId, - }, + "Missing userId for credential resolution in file manager", + { + operation: "ssh_credentials", + hostId, + credentialId, + hasUserId: !!userId, + }, ); } @@ -279,21 +275,29 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { }, }; - if (resolvedCredentials.authType === "password" && resolvedCredentials.password && resolvedCredentials.password.trim()) { + if ( + resolvedCredentials.authType === "password" && + resolvedCredentials.password && + resolvedCredentials.password.trim() + ) { config.password = resolvedCredentials.password; - } else if (resolvedCredentials.authType === "key" && resolvedCredentials.sshKey && resolvedCredentials.sshKey.trim()) { + } else if ( + resolvedCredentials.authType === "key" && + resolvedCredentials.sshKey && + resolvedCredentials.sshKey.trim() + ) { try { if ( - !resolvedCredentials.sshKey.includes("-----BEGIN") || - !resolvedCredentials.sshKey.includes("-----END") + !resolvedCredentials.sshKey.includes("-----BEGIN") || + !resolvedCredentials.sshKey.includes("-----END") ) { throw new Error("Invalid private key format"); } const cleanKey = resolvedCredentials.sshKey - .trim() - .replace(/\r\n/g, "\n") - .replace(/\r/g, "\n"); + .trim() + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n"); config.privateKey = Buffer.from(cleanKey, "utf8"); @@ -309,17 +313,20 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { return res.status(400).json({ error: "Invalid SSH key format" }); } } else { - fileLogger.warn("No valid authentication method provided for file manager", { - operation: "file_connect", - sessionId, - hostId, - authType: resolvedCredentials.authType, - hasPassword: !!resolvedCredentials.password, - hasKey: !!resolvedCredentials.sshKey, - }); + fileLogger.warn( + "No valid authentication method provided for file manager", + { + operation: "file_connect", + sessionId, + hostId, + authType: resolvedCredentials.authType, + hasPassword: !!resolvedCredentials.password, + hasKey: !!resolvedCredentials.sshKey, + }, + ); return res - .status(400) - .json({ error: "Either password or SSH key must be provided" }); + .status(400) + .json({ error: "Either password or SSH key must be provided" }); } let responseSent = false; @@ -359,14 +366,12 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { client.connect(config); }); - app.post("/ssh/file_manager/ssh/disconnect", (req, res) => { const { sessionId } = req.body; cleanupSession(sessionId); res.json({ status: "success", message: "SSH connection disconnected" }); }); - app.get("/ssh/file_manager/ssh/status", (req, res) => { const sessionId = req.query.sessionId as string; const isConnected = !!sshSessions[sessionId]?.isConnected; @@ -400,7 +405,6 @@ app.post("/ssh/file_manager/ssh/keepalive", (req, res) => { }); }); - app.get("/ssh/file_manager/ssh/listFiles", (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; @@ -437,7 +441,7 @@ app.get("/ssh/file_manager/ssh/listFiles", (req, res) => { stream.on("close", (code) => { if (code !== 0) { fileLogger.error( - `SSH listFiles command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH listFiles command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); return res.status(500).json({ error: `Command failed: ${errorData}` }); } @@ -487,9 +491,9 @@ app.get("/ssh/file_manager/ssh/listFiles", (req, res) => { linkTarget, path: `${sshPath.endsWith("/") ? sshPath : sshPath + "/"}${actualName}`, executable: - !isDirectory && !isLink - ? isExecutableFile(permissions, actualName) - : false, + !isDirectory && !isLink + ? isExecutableFile(permissions, actualName) + : false, }); } } @@ -499,7 +503,6 @@ app.get("/ssh/file_manager/ssh/listFiles", (req, res) => { }); }); - app.get("/ssh/file_manager/ssh/identifySymlink", (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; @@ -542,7 +545,7 @@ app.get("/ssh/file_manager/ssh/identifySymlink", (req, res) => { stream.on("close", (code) => { if (code !== 0) { fileLogger.error( - `SSH identifySymlink command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH identifySymlink command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); return res.status(500).json({ error: `Command failed: ${errorData}` }); } @@ -553,8 +556,8 @@ app.get("/ssh/file_manager/ssh/identifySymlink", (req, res) => { path: linkPath, target: target, type: fileType.toLowerCase().includes("directory") - ? "directory" - : "file", + ? "directory" + : "file", }); }); @@ -567,7 +570,6 @@ app.get("/ssh/file_manager/ssh/identifySymlink", (req, res) => { }); }); - app.get("/ssh/file_manager/ssh/readFile", (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; @@ -591,106 +593,105 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => { const escapedPath = filePath.replace(/'/g, "'\"'\"'"); sshConn.client.exec( - `stat -c%s '${escapedPath}' 2>/dev/null || wc -c < '${escapedPath}'`, - (sizeErr, sizeStream) => { - if (sizeErr) { - fileLogger.error("SSH file size check error:", sizeErr); - return res.status(500).json({ error: sizeErr.message }); + `stat -c%s '${escapedPath}' 2>/dev/null || wc -c < '${escapedPath}'`, + (sizeErr, sizeStream) => { + if (sizeErr) { + fileLogger.error("SSH file size check error:", sizeErr); + return res.status(500).json({ error: sizeErr.message }); + } + + let sizeData = ""; + let sizeErrorData = ""; + + sizeStream.on("data", (chunk: Buffer) => { + sizeData += chunk.toString(); + }); + + sizeStream.stderr.on("data", (chunk: Buffer) => { + sizeErrorData += chunk.toString(); + }); + + sizeStream.on("close", (sizeCode) => { + if (sizeCode !== 0) { + const errorLower = sizeErrorData.toLowerCase(); + const isFileNotFound = + errorLower.includes("no such file or directory") || + errorLower.includes("cannot access") || + errorLower.includes("not found") || + errorLower.includes("resource not found"); + + fileLogger.error(`File size check failed: ${sizeErrorData}`); + return res.status(isFileNotFound ? 404 : 500).json({ + error: `Cannot check file size: ${sizeErrorData}`, + fileNotFound: isFileNotFound, + }); } - let sizeData = ""; - let sizeErrorData = ""; + const fileSize = parseInt(sizeData.trim(), 10); - sizeStream.on("data", (chunk: Buffer) => { - sizeData += chunk.toString(); - }); + if (isNaN(fileSize)) { + fileLogger.error("Invalid file size response:", sizeData); + return res.status(500).json({ error: "Cannot determine file size" }); + } - sizeStream.stderr.on("data", (chunk: Buffer) => { - sizeErrorData += chunk.toString(); - }); + if (fileSize > MAX_READ_SIZE) { + fileLogger.warn("File too large for reading", { + operation: "file_read", + sessionId, + filePath, + fileSize, + maxSize: MAX_READ_SIZE, + }); + return res.status(400).json({ + error: `File too large to open in editor. Maximum size is ${MAX_READ_SIZE / 1024 / 1024}MB, file is ${(fileSize / 1024 / 1024).toFixed(2)}MB. Use download instead.`, + fileSize, + maxSize: MAX_READ_SIZE, + tooLarge: true, + }); + } - sizeStream.on("close", (sizeCode) => { - if (sizeCode !== 0) { - const errorLower = sizeErrorData.toLowerCase(); - const isFileNotFound = - errorLower.includes("no such file or directory") || - errorLower.includes("cannot access") || - errorLower.includes("not found") || - errorLower.includes("resource not found"); - - fileLogger.error(`File size check failed: ${sizeErrorData}`); - return res.status(isFileNotFound ? 404 : 500).json({ - error: `Cannot check file size: ${sizeErrorData}`, - fileNotFound: isFileNotFound, - }); + sshConn.client.exec(`cat '${escapedPath}'`, (err, stream) => { + if (err) { + fileLogger.error("SSH readFile error:", err); + return res.status(500).json({ error: err.message }); } - const fileSize = parseInt(sizeData.trim(), 10); + let data = ""; + let errorData = ""; - if (isNaN(fileSize)) { - fileLogger.error("Invalid file size response:", sizeData); - return res.status(500).json({ error: "Cannot determine file size" }); - } + stream.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); - if (fileSize > MAX_READ_SIZE) { - fileLogger.warn("File too large for reading", { - operation: "file_read", - sessionId, - filePath, - fileSize, - maxSize: MAX_READ_SIZE, - }); - return res.status(400).json({ - error: `File too large to open in editor. Maximum size is ${MAX_READ_SIZE / 1024 / 1024}MB, file is ${(fileSize / 1024 / 1024).toFixed(2)}MB. Use download instead.`, - fileSize, - maxSize: MAX_READ_SIZE, - tooLarge: true, - }); - } + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); - sshConn.client.exec(`cat '${escapedPath}'`, (err, stream) => { - if (err) { - fileLogger.error("SSH readFile error:", err); - return res.status(500).json({ error: err.message }); + stream.on("close", (code) => { + if (code !== 0) { + fileLogger.error( + `SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + + const isFileNotFound = + errorData.includes("No such file or directory") || + errorData.includes("cannot access") || + errorData.includes("not found"); + + return res.status(isFileNotFound ? 404 : 500).json({ + error: `Command failed: ${errorData}`, + fileNotFound: isFileNotFound, + }); } - let data = ""; - let errorData = ""; - - stream.on("data", (chunk: Buffer) => { - data += chunk.toString(); - }); - - stream.stderr.on("data", (chunk: Buffer) => { - errorData += chunk.toString(); - }); - - stream.on("close", (code) => { - if (code !== 0) { - fileLogger.error( - `SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, - ); - - const isFileNotFound = - errorData.includes("No such file or directory") || - errorData.includes("cannot access") || - errorData.includes("not found"); - - return res.status(isFileNotFound ? 404 : 500).json({ - error: `Command failed: ${errorData}`, - fileNotFound: isFileNotFound, - }); - } - - res.json({ content: data, path: filePath }); - }); + res.json({ content: data, path: filePath }); }); }); - }, + }); + }, ); }); - app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { const { sessionId, path: filePath, content, hostId, userId } = req.body; const sshConn = sshSessions[sessionId]; @@ -718,7 +719,7 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { sshConn.client.sftp((err, sftp) => { if (err) { fileLogger.warn( - `SFTP failed, trying fallback method: ${err.message}`, + `SFTP failed, trying fallback method: ${err.message}`, ); tryFallbackMethod(); return; @@ -737,8 +738,8 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { fileLogger.error("Buffer conversion error:", bufferErr); if (!res.headersSent) { return res - .status(500) - .json({ error: "Invalid file content format" }); + .status(500) + .json({ error: "Invalid file content format" }); } return; } @@ -752,7 +753,7 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { if (hasError || hasFinished) return; hasError = true; fileLogger.warn( - `SFTP write failed, trying fallback method: ${streamErr.message}`, + `SFTP write failed, trying fallback method: ${streamErr.message}`, ); tryFallbackMethod(); }); @@ -788,14 +789,14 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { if (hasError || hasFinished) return; hasError = true; fileLogger.warn( - `SFTP write operation failed, trying fallback method: ${writeErr.message}`, + `SFTP write operation failed, trying fallback method: ${writeErr.message}`, ); tryFallbackMethod(); } }); } catch (sftpErr) { fileLogger.warn( - `SFTP connection error, trying fallback method: ${sftpErr.message}`, + `SFTP connection error, trying fallback method: ${sftpErr.message}`, ); tryFallbackMethod(); } @@ -845,7 +846,7 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { } } else { fileLogger.error( - `Fallback write failed with code ${code}: ${errorData}`, + `Fallback write failed with code ${code}: ${errorData}`, ); if (!res.headersSent) { res.status(500).json({ @@ -860,8 +861,8 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { fileLogger.error("Fallback write stream error:", streamErr); if (!res.headersSent) { res - .status(500) - .json({ error: `Write stream error: ${streamErr.message}` }); + .status(500) + .json({ error: `Write stream error: ${streamErr.message}` }); } }); }); @@ -869,8 +870,8 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { fileLogger.error("Fallback method failed:", fallbackErr); if (!res.headersSent) { res - .status(500) - .json({ error: `All write methods failed: ${fallbackErr.message}` }); + .status(500) + .json({ error: `All write methods failed: ${fallbackErr.message}` }); } } }; @@ -878,7 +879,6 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { trySFTP(); }); - app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { const { sessionId, @@ -900,27 +900,27 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { if (!filePath || !fileName || content === undefined) { return res - .status(400) - .json({ error: "File path, name, and content are required" }); + .status(400) + .json({ error: "File path, name, and content are required" }); } sshConn.lastActive = Date.now(); const contentSize = - typeof content === "string" - ? Buffer.byteLength(content, "utf8") - : content.length; + typeof content === "string" + ? Buffer.byteLength(content, "utf8") + : content.length; const fullPath = filePath.endsWith("/") - ? filePath + fileName - : filePath + "/" + fileName; + ? filePath + fileName + : filePath + "/" + fileName; const trySFTP = () => { try { sshConn.client.sftp((err, sftp) => { if (err) { fileLogger.warn( - `SFTP failed, trying fallback method: ${err.message}`, + `SFTP failed, trying fallback method: ${err.message}`, ); tryFallbackMethod(); return; @@ -939,8 +939,8 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { fileLogger.error("Buffer conversion error:", bufferErr); if (!res.headersSent) { return res - .status(500) - .json({ error: "Invalid file content format" }); + .status(500) + .json({ error: "Invalid file content format" }); } return; } @@ -954,14 +954,14 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { if (hasError || hasFinished) return; hasError = true; fileLogger.warn( - `SFTP write failed, trying fallback method: ${streamErr.message}`, - { - operation: "file_upload", - sessionId, - fileName, - fileSize: contentSize, - error: streamErr.message, - }, + `SFTP write failed, trying fallback method: ${streamErr.message}`, + { + operation: "file_upload", + sessionId, + fileName, + fileSize: contentSize, + error: streamErr.message, + }, ); tryFallbackMethod(); }); @@ -997,14 +997,14 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { if (hasError || hasFinished) return; hasError = true; fileLogger.warn( - `SFTP write operation failed, trying fallback method: ${writeErr.message}`, + `SFTP write operation failed, trying fallback method: ${writeErr.message}`, ); tryFallbackMethod(); } }); } catch (sftpErr) { fileLogger.warn( - `SFTP connection error, trying fallback method: ${sftpErr.message}`, + `SFTP connection error, trying fallback method: ${sftpErr.message}`, ); tryFallbackMethod(); } @@ -1032,8 +1032,8 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { fileLogger.error("Fallback upload command failed:", err); if (!res.headersSent) { return res - .status(500) - .json({ error: `Upload failed: ${err.message}` }); + .status(500) + .json({ error: `Upload failed: ${err.message}` }); } return; } @@ -1063,7 +1063,7 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { } } else { fileLogger.error( - `Fallback upload failed with code ${code}: ${errorData}`, + `Fallback upload failed with code ${code}: ${errorData}`, ); if (!res.headersSent) { res.status(500).json({ @@ -1081,8 +1081,8 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { fileLogger.error("Fallback upload stream error:", streamErr); if (!res.headersSent) { res - .status(500) - .json({ error: `Upload stream error: ${streamErr.message}` }); + .status(500) + .json({ error: `Upload stream error: ${streamErr.message}` }); } }); }); @@ -1104,8 +1104,8 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { fileLogger.error("Chunked fallback upload failed:", err); if (!res.headersSent) { return res - .status(500) - .json({ error: `Chunked upload failed: ${err.message}` }); + .status(500) + .json({ error: `Chunked upload failed: ${err.message}` }); } return; } @@ -1135,7 +1135,7 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { } } else { fileLogger.error( - `Chunked fallback upload failed with code ${code}: ${errorData}`, + `Chunked fallback upload failed with code ${code}: ${errorData}`, ); if (!res.headersSent) { res.status(500).json({ @@ -1151,8 +1151,8 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { stream.on("error", (streamErr) => { fileLogger.error( - "Chunked fallback upload stream error:", - streamErr, + "Chunked fallback upload stream error:", + streamErr, ); if (!res.headersSent) { res.status(500).json({ @@ -1166,8 +1166,8 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { fileLogger.error("Fallback method failed:", fallbackErr); if (!res.headersSent) { res - .status(500) - .json({ error: `All upload methods failed: ${fallbackErr.message}` }); + .status(500) + .json({ error: `All upload methods failed: ${fallbackErr.message}` }); } } }; @@ -1175,7 +1175,6 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { trySFTP(); }); - app.post("/ssh/file_manager/ssh/createFile", async (req, res) => { const { sessionId, @@ -1202,8 +1201,8 @@ app.post("/ssh/file_manager/ssh/createFile", async (req, res) => { sshConn.lastActive = Date.now(); const fullPath = filePath.endsWith("/") - ? filePath + fileName - : filePath + "/" + fileName; + ? filePath + fileName + : filePath + "/" + fileName; const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); const createCommand = `touch '${escapedPath}' && echo "SUCCESS" && exit 0`; @@ -1252,7 +1251,7 @@ app.post("/ssh/file_manager/ssh/createFile", async (req, res) => { if (code !== 0) { fileLogger.error( - `SSH createFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH createFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); if (!res.headersSent) { return res.status(500).json({ @@ -1284,7 +1283,6 @@ app.post("/ssh/file_manager/ssh/createFile", async (req, res) => { }); }); - app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => { const { sessionId, path: folderPath, folderName, hostId, userId } = req.body; const sshConn = sshSessions[sessionId]; @@ -1304,8 +1302,8 @@ app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => { sshConn.lastActive = Date.now(); const fullPath = folderPath.endsWith("/") - ? folderPath + folderName - : folderPath + "/" + folderName; + ? folderPath + folderName + : folderPath + "/" + folderName; const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); const createCommand = `mkdir -p '${escapedPath}' && echo "SUCCESS" && exit 0`; @@ -1354,7 +1352,7 @@ app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => { if (code !== 0) { fileLogger.error( - `SSH createFolder command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH createFolder command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); if (!res.headersSent) { return res.status(500).json({ @@ -1406,8 +1404,8 @@ app.delete("/ssh/file_manager/ssh/deleteItem", async (req, res) => { const escapedPath = itemPath.replace(/'/g, "'\"'\"'"); const deleteCommand = isDirectory - ? `rm -rf '${escapedPath}' && echo "SUCCESS" && exit 0` - : `rm -f '${escapedPath}' && echo "SUCCESS" && exit 0`; + ? `rm -rf '${escapedPath}' && echo "SUCCESS" && exit 0` + : `rm -f '${escapedPath}' && echo "SUCCESS" && exit 0`; sshConn.client.exec(deleteCommand, (err, stream) => { if (err) { @@ -1456,7 +1454,7 @@ app.delete("/ssh/file_manager/ssh/deleteItem", async (req, res) => { if (code !== 0) { fileLogger.error( - `SSH deleteItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH deleteItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); if (!res.headersSent) { return res.status(500).json({ @@ -1502,8 +1500,8 @@ app.put("/ssh/file_manager/ssh/renameItem", async (req, res) => { if (!oldPath || !newName) { return res - .status(400) - .json({ error: "Old path and new name are required" }); + .status(400) + .json({ error: "Old path and new name are required" }); } sshConn.lastActive = Date.now(); @@ -1563,7 +1561,7 @@ app.put("/ssh/file_manager/ssh/renameItem", async (req, res) => { if (code !== 0) { fileLogger.error( - `SSH renameItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH renameItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); if (!res.headersSent) { return res.status(500).json({ @@ -1610,8 +1608,8 @@ app.put("/ssh/file_manager/ssh/moveItem", async (req, res) => { if (!oldPath || !newPath) { return res - .status(400) - .json({ error: "Old path and new path are required" }); + .status(400) + .json({ error: "Old path and new path are required" }); } sshConn.lastActive = Date.now(); @@ -1687,7 +1685,7 @@ app.put("/ssh/file_manager/ssh/moveItem", async (req, res) => { if (code !== 0) { fileLogger.error( - `SSH moveItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + `SSH moveItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); if (!res.headersSent) { return res.status(500).json({ @@ -1721,7 +1719,6 @@ app.put("/ssh/file_manager/ssh/moveItem", async (req, res) => { }); }); - app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => { const { sessionId, path: filePath, hostId, userId } = req.body; @@ -1742,8 +1739,8 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => { isConnected: sshConn?.isConnected, }); return res - .status(400) - .json({ error: "SSH session not found or not connected" }); + .status(400) + .json({ error: "SSH session not found or not connected" }); } sshConn.lastActive = Date.now(); @@ -1759,8 +1756,8 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => { if (statErr) { fileLogger.error("File stat failed for download:", statErr); return res - .status(500) - .json({ error: `Cannot access file: ${statErr.message}` }); + .status(500) + .json({ error: `Cannot access file: ${statErr.message}` }); } if (!stats.isFile()) { @@ -1772,8 +1769,8 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => { isDirectory: stats.isDirectory(), }); return res - .status(400) - .json({ error: "Cannot download directories or special files" }); + .status(400) + .json({ error: "Cannot download directories or special files" }); } const MAX_FILE_SIZE = 5 * 1024 * 1024 * 1024; @@ -1794,8 +1791,8 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => { if (readErr) { fileLogger.error("File read failed for download:", readErr); return res - .status(500) - .json({ error: `Failed to read file: ${readErr.message}` }); + .status(500) + .json({ error: `Failed to read file: ${readErr.message}` }); } const base64Content = data.toString("base64"); @@ -1823,7 +1820,6 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => { }); }); - app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => { const { sessionId, sourcePath, targetDir, hostId, userId } = req.body; @@ -1834,8 +1830,8 @@ app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => { const sshConn = sshSessions[sessionId]; if (!sshConn || !sshConn.isConnected) { return res - .status(400) - .json({ error: "SSH session not found or not connected" }); + .status(400) + .json({ error: "SSH session not found or not connected" }); } sshConn.lastActive = Date.now(); @@ -1895,7 +1891,7 @@ app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => { if (code !== 0) { const fullErrorInfo = - errorData || stdoutData || "No error message available"; + errorData || stdoutData || "No error message available"; fileLogger.error(`SSH copyItem command failed with code ${code}`, { operation: "file_copy_failed", sessionId, @@ -1926,7 +1922,7 @@ app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => { } const copySuccessful = - stdoutData.includes("COPY_SUCCESS") || code === 0; + stdoutData.includes("COPY_SUCCESS") || code === 0; if (copySuccessful) { fileLogger.success("Item copied successfully", { @@ -1993,13 +1989,13 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => { if (!sshConn || !sshConn.isConnected) { fileLogger.error( - "SSH connection not found or not connected for executeFile", - { - operation: "execute_file", - sessionId, - hasConnection: !!sshConn, - isConnected: sshConn?.isConnected, - }, + "SSH connection not found or not connected for executeFile", + { + operation: "execute_file", + sessionId, + hasConnection: !!sshConn, + isConnected: sshConn?.isConnected, + }, ); return res.status(400).json({ error: "SSH connection not available" }); } @@ -2016,8 +2012,8 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => { if (checkErr) { fileLogger.error("SSH executeFile check error:", checkErr); return res - .status(500) - .json({ error: "Failed to check file executability" }); + .status(500) + .json({ error: "Failed to check file executability" }); } let checkResult = ""; @@ -2052,8 +2048,8 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => { stream.on("close", (code) => { const exitCodeMatch = output.match(/EXIT_CODE:(\d+)$/); const actualExitCode = exitCodeMatch - ? parseInt(exitCodeMatch[1]) - : code; + ? parseInt(exitCodeMatch[1]) + : code; const cleanOutput = output.replace(/EXIT_CODE:\d+$/, "").trim(); fileLogger.info("File execution completed", { @@ -2085,7 +2081,6 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => { }); }); - process.on("SIGINT", () => { Object.keys(sshSessions).forEach(cleanupSession); process.exit(0); @@ -2096,10 +2091,8 @@ process.on("SIGTERM", () => { process.exit(0); }); - const PORT = 30004; - try { const server = app.listen(PORT, async () => { try { @@ -2111,7 +2104,7 @@ try { } }); - server.on('error', (err) => { + server.on("error", (err) => { fileLogger.error("File Manager server error", err, { operation: "file_manager_server_error", port: PORT, diff --git a/src/backend/ssh/terminal.ts b/src/backend/ssh/terminal.ts index 7889bfa8..405afabb 100644 --- a/src/backend/ssh/terminal.ts +++ b/src/backend/ssh/terminal.ts @@ -588,9 +588,15 @@ wss.on("connection", async (ws: WebSocket, req) => { compress: ["none", "zlib@openssh.com", "zlib"], }, }; - if (resolvedCredentials.authType === "password" && resolvedCredentials.password) { + if ( + resolvedCredentials.authType === "password" && + resolvedCredentials.password + ) { connectConfig.password = resolvedCredentials.password; - } else if (resolvedCredentials.authType === "key" && resolvedCredentials.key) { + } else if ( + resolvedCredentials.authType === "key" && + resolvedCredentials.key + ) { try { if ( !resolvedCredentials.key.includes("-----BEGIN") || diff --git a/src/backend/starter.ts b/src/backend/starter.ts index 02c865df..fb0cfc89 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -24,13 +24,15 @@ import { systemLogger, versionLogger } from "./utils/logger.js"; } catch {} let version = "unknown"; - + const versionSources = [ () => process.env.VERSION, () => { try { const packageJsonPath = path.join(process.cwd(), "package.json"); - const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + const packageJson = JSON.parse( + readFileSync(packageJsonPath, "utf-8"), + ); return packageJson.version; } catch { return null; @@ -43,7 +45,9 @@ import { systemLogger, versionLogger } from "./utils/logger.js"; path.dirname(__filename), "../../../package.json", ); - const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + const packageJson = JSON.parse( + readFileSync(packageJsonPath, "utf-8"), + ); return packageJson.version; } catch { return null; @@ -52,12 +56,14 @@ import { systemLogger, versionLogger } from "./utils/logger.js"; () => { try { const packageJsonPath = path.join("/app", "package.json"); - const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + const packageJson = JSON.parse( + readFileSync(packageJsonPath, "utf-8"), + ); return packageJson.version; } catch { return null; } - } + }, ]; for (const getVersion of versionSources) { diff --git a/src/backend/utils/auto-ssl-setup.ts b/src/backend/utils/auto-ssl-setup.ts index 730a4b80..e2d1034a 100644 --- a/src/backend/utils/auto-ssl-setup.ts +++ b/src/backend/utils/auto-ssl-setup.ts @@ -37,13 +37,13 @@ export class AutoSSLSetup { try { await fs.access(this.CERT_FILE); await fs.access(this.KEY_FILE); - + systemLogger.info("SSL certificates found from entrypoint script", { operation: "ssl_cert_found_entrypoint", cert_path: this.CERT_FILE, key_path: this.KEY_FILE, }); - + await this.logCertificateInfo(); await this.setupEnvironmentVariables(); return; diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index 1608207b..557077ac 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -3,45 +3,53 @@ import { Toaster as Sonner, type ToasterProps, toast } from "sonner"; import { useRef } from "react"; const Toaster = ({ ...props }: ToasterProps) => { - const { theme = "system" } = useTheme(); - const lastToastRef = useRef<{ text: string; timestamp: number } | null>(null); + const { theme = "system" } = useTheme(); + const lastToastRef = useRef<{ text: string; timestamp: number } | null>(null); - const originalToast = toast; + const originalToast = toast; - const rateLimitedToast = (message: string, options?: any) => { - const now = Date.now(); - const lastToast = lastToastRef.current; + const rateLimitedToast = (message: string, options?: any) => { + const now = Date.now(); + const lastToast = lastToastRef.current; - if (lastToast && lastToast.text === message && (now - lastToast.timestamp) < 1000) { - return; - } + if ( + lastToast && + lastToast.text === message && + now - lastToast.timestamp < 1000 + ) { + return; + } - lastToastRef.current = { text: message, timestamp: now }; - return originalToast(message, options); - }; + lastToastRef.current = { text: message, timestamp: now }; + return originalToast(message, options); + }; - Object.assign(toast, { - success: (message: string, options?: any) => rateLimitedToast(message, { ...options, type: 'success' }), - error: (message: string, options?: any) => rateLimitedToast(message, { ...options, type: 'error' }), - warning: (message: string, options?: any) => rateLimitedToast(message, { ...options, type: 'warning' }), - info: (message: string, options?: any) => rateLimitedToast(message, { ...options, type: 'info' }), - message: rateLimitedToast, - }); + Object.assign(toast, { + success: (message: string, options?: any) => + rateLimitedToast(message, { ...options, type: "success" }), + error: (message: string, options?: any) => + rateLimitedToast(message, { ...options, type: "error" }), + warning: (message: string, options?: any) => + rateLimitedToast(message, { ...options, type: "warning" }), + info: (message: string, options?: any) => + rateLimitedToast(message, { ...options, type: "info" }), + message: rateLimitedToast, + }); - return ( - - ); + return ( + + ); }; -export { Toaster }; \ No newline at end of file +export { Toaster }; diff --git a/src/components/ui/version-alert.tsx b/src/components/ui/version-alert.tsx index 897d49d6..45fcc780 100644 --- a/src/components/ui/version-alert.tsx +++ b/src/components/ui/version-alert.tsx @@ -24,10 +24,7 @@ interface VersionAlertProps { onDownload?: () => void; } -export function VersionAlert({ - updateInfo, - onDownload, -}: VersionAlertProps) { +export function VersionAlert({ updateInfo, onDownload }: VersionAlertProps) { const { t } = useTranslation(); if (!updateInfo.success) { diff --git a/src/components/ui/version-check-modal.tsx b/src/components/ui/version-check-modal.tsx index d6cd3039..29d20aa9 100644 --- a/src/components/ui/version-check-modal.tsx +++ b/src/components/ui/version-check-modal.tsx @@ -11,7 +11,11 @@ interface VersionCheckModalProps { isAuthenticated?: boolean; } -export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = false }: VersionCheckModalProps) { +export function VersionCheckModal({ + onDismiss, + onContinue, + isAuthenticated = false, +}: VersionCheckModalProps) { const { t } = useTranslation(); const [versionInfo, setVersionInfo] = useState(null); const [versionChecking, setVersionChecking] = useState(false); @@ -30,7 +34,7 @@ export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = fal try { const updateInfo = await checkElectronUpdate(); setVersionInfo(updateInfo); - + if (updateInfo?.status === "up_to_date") { onContinue(); return; @@ -65,7 +69,7 @@ export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = fal return (
{!isAuthenticated && ( -
{!isAuthenticated && ( -
- + {versionInfo && !versionDismissed && (
-
@@ -146,7 +146,7 @@ export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = fal return (
{!isAuthenticated && ( -
- +
-
diff --git a/src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx b/src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx index 545192c2..553f228b 100644 --- a/src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx +++ b/src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx @@ -107,7 +107,10 @@ export function CredentialsManager({ setHostSearchQuery(""); setSelectedHostId(""); setTimeout(() => { - if (document.activeElement && (document.activeElement as HTMLElement).blur) { + if ( + document.activeElement && + (document.activeElement as HTMLElement).blur + ) { (document.activeElement as HTMLElement).blur(); } }, 50); diff --git a/src/ui/Desktop/Homepage/HomepageAuth.tsx b/src/ui/Desktop/Homepage/HomepageAuth.tsx index c73bad1a..7184e7a7 100644 --- a/src/ui/Desktop/Homepage/HomepageAuth.tsx +++ b/src/ui/Desktop/Homepage/HomepageAuth.tsx @@ -222,7 +222,8 @@ export function HomepageAuth({ setTotpCode(""); setTotpTempToken(""); } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || t("errors.unknownError"); + const errorMessage = + err?.response?.data?.error || err?.message || t("errors.unknownError"); toast.error(errorMessage); setInternalLoggedIn(false); setLoggedIn(false); @@ -370,7 +371,10 @@ export function HomepageAuth({ setTotpTempToken(""); toast.success(t("messages.loginSuccess")); } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || t("errors.invalidTotpCode"); + const errorMessage = + err?.response?.data?.error || + err?.message || + t("errors.invalidTotpCode"); toast.error(errorMessage); } finally { setTotpLoading(false); @@ -390,7 +394,10 @@ export function HomepageAuth({ window.location.replace(authUrl); } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || t("errors.failedOidcLogin"); + const errorMessage = + err?.response?.data?.error || + err?.message || + t("errors.failedOidcLogin"); toast.error(errorMessage); setOidcLoading(false); } diff --git a/src/ui/Mobile/Homepage/HomepageAuth.tsx b/src/ui/Mobile/Homepage/HomepageAuth.tsx index 9babecbe..d740573f 100644 --- a/src/ui/Mobile/Homepage/HomepageAuth.tsx +++ b/src/ui/Mobile/Homepage/HomepageAuth.tsx @@ -204,7 +204,8 @@ export function HomepageAuth({ setTotpCode(""); setTotpTempToken(""); } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || t("errors.unknownError"); + const errorMessage = + err?.response?.data?.error || err?.message || t("errors.unknownError"); toast.error(errorMessage); setInternalLoggedIn(false); setLoggedIn(false); @@ -346,7 +347,10 @@ export function HomepageAuth({ setTotpTempToken(""); toast.success(t("messages.loginSuccess")); } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || t("errors.invalidTotpCode"); + const errorMessage = + err?.response?.data?.error || + err?.message || + t("errors.invalidTotpCode"); toast.error(errorMessage); } finally { setTotpLoading(false); @@ -366,7 +370,10 @@ export function HomepageAuth({ window.location.replace(authUrl); } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || t("errors.failedOidcLogin"); + const errorMessage = + err?.response?.data?.error || + err?.message || + t("errors.failedOidcLogin"); toast.error(errorMessage); setOidcLoading(false); } diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index e1cecf98..2486d99f 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -168,10 +168,10 @@ function createApiInstance( if (process.env.NODE_ENV === "development") { logger.requestStart(method, fullUrl, context); } - + if (isElectron()) { config.headers["X-Electron-App"] = "true"; - + const token = localStorage.getItem("jwt"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; @@ -304,7 +304,7 @@ function isDev(): boolean { if (isElectron()) { return false; } - + return ( process.env.NODE_ENV === "development" && (window.location.port === "3000" || @@ -463,16 +463,21 @@ export let statsApi: AxiosInstance; export let authApi: AxiosInstance; if (isElectron()) { - getServerConfig().then((config) => { - if (config?.serverUrl) { - configuredServerUrl = config.serverUrl; - (window as any).configuredServerUrl = configuredServerUrl; - } - initializeApiInstances(); - }).catch((error) => { - console.error("Failed to load server config, initializing with default:", error); - initializeApiInstances(); - }); + getServerConfig() + .then((config) => { + if (config?.serverUrl) { + configuredServerUrl = config.serverUrl; + (window as any).configuredServerUrl = configuredServerUrl; + } + initializeApiInstances(); + }) + .catch((error) => { + console.error( + "Failed to load server config, initializing with default:", + error, + ); + initializeApiInstances(); + }); } else { initializeApiInstances(); } @@ -535,15 +540,13 @@ function handleApiError(error: unknown, operation: string): never { `Auth failed: ${method} ${url} - ${message}`, errorContext, ); - - const isLoginEndpoint = url?.includes('/users/login'); - const errorMessage = isLoginEndpoint ? message : "Authentication required. Please log in again."; - - throw new ApiError( - errorMessage, - 401, - "AUTH_REQUIRED", - ); + + const isLoginEndpoint = url?.includes("/users/login"); + const errorMessage = isLoginEndpoint + ? message + : "Authentication required. Please log in again."; + + throw new ApiError(errorMessage, 401, "AUTH_REQUIRED"); } else if (status === 403) { authLogger.warn(`Access denied: ${method} ${url}`, errorContext); throw new ApiError( @@ -1527,11 +1530,11 @@ export async function loginUser( ): Promise { try { const response = await authApi.post("/users/login", { username, password }); - + if (isElectron() && response.data.token) { localStorage.setItem("jwt", response.data.token); } - + return { token: response.data.token || "cookie-based", success: response.data.success,