diff --git a/src/backend/database/routes/ssh.ts b/src/backend/database/routes/ssh.ts index 679ec722..b6a4294e 100644 --- a/src/backend/database/routes/ssh.ts +++ b/src/backend/database/routes/ssh.ts @@ -13,6 +13,7 @@ import { recentActivity, hostAccess, userRoles, + sessionRecordings, } from "../db/schema.js"; import { eq, @@ -1069,60 +1070,40 @@ router.delete( const numericHostId = Number(hostId); + // Delete all related data in correct order (child tables first) await db .delete(fileManagerRecent) - .where( - and( - eq(fileManagerRecent.hostId, numericHostId), - eq(fileManagerRecent.userId, userId), - ), - ); + .where(eq(fileManagerRecent.hostId, numericHostId)); await db .delete(fileManagerPinned) - .where( - and( - eq(fileManagerPinned.hostId, numericHostId), - eq(fileManagerPinned.userId, userId), - ), - ); + .where(eq(fileManagerPinned.hostId, numericHostId)); await db .delete(fileManagerShortcuts) - .where( - and( - eq(fileManagerShortcuts.hostId, numericHostId), - eq(fileManagerShortcuts.userId, userId), - ), - ); + .where(eq(fileManagerShortcuts.hostId, numericHostId)); await db .delete(commandHistory) - .where( - and( - eq(commandHistory.hostId, numericHostId), - eq(commandHistory.userId, userId), - ), - ); + .where(eq(commandHistory.hostId, numericHostId)); await db .delete(sshCredentialUsage) - .where( - and( - eq(sshCredentialUsage.hostId, numericHostId), - eq(sshCredentialUsage.userId, userId), - ), - ); + .where(eq(sshCredentialUsage.hostId, numericHostId)); await db .delete(recentActivity) - .where( - and( - eq(recentActivity.hostId, numericHostId), - eq(recentActivity.userId, userId), - ), - ); + .where(eq(recentActivity.hostId, numericHostId)); + // Delete RBAC host access entries + await db.delete(hostAccess).where(eq(hostAccess.hostId, numericHostId)); + + // Delete session recordings + await db + .delete(sessionRecordings) + .where(eq(sessionRecordings.hostId, numericHostId)); + + // Finally delete the host itself await db .delete(sshData) .where(and(eq(sshData.id, numericHostId), eq(sshData.userId, userId))); @@ -1887,10 +1868,49 @@ router.delete( }); } + const hostIds = hostsToDelete.map((host) => host.id); + + // Delete all related data for all hosts in the folder (child tables first) + if (hostIds.length > 0) { + await db + .delete(fileManagerRecent) + .where(inArray(fileManagerRecent.hostId, hostIds)); + + await db + .delete(fileManagerPinned) + .where(inArray(fileManagerPinned.hostId, hostIds)); + + await db + .delete(fileManagerShortcuts) + .where(inArray(fileManagerShortcuts.hostId, hostIds)); + + await db + .delete(commandHistory) + .where(inArray(commandHistory.hostId, hostIds)); + + await db + .delete(sshCredentialUsage) + .where(inArray(sshCredentialUsage.hostId, hostIds)); + + await db + .delete(recentActivity) + .where(inArray(recentActivity.hostId, hostIds)); + + // Delete RBAC host access entries + await db.delete(hostAccess).where(inArray(hostAccess.hostId, hostIds)); + + // Delete session recordings + await db + .delete(sessionRecordings) + .where(inArray(sessionRecordings.hostId, hostIds)); + } + + // Now delete the hosts themselves await db .delete(sshData) .where(and(eq(sshData.userId, userId), eq(sshData.folder, folderName))); + // Finally delete the folder metadata await db .delete(sshFolders) .where( diff --git a/src/backend/ssh/file-manager.ts b/src/backend/ssh/file-manager.ts index 0b52f844..10eae574 100644 --- a/src/backend/ssh/file-manager.ts +++ b/src/backend/ssh/file-manager.ts @@ -824,12 +824,17 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { useSocks5, socks5Host, socks5Port, - hasSocks5ProxyChain: !!(socks5ProxyChain && (socks5ProxyChain as any).length > 0), + hasSocks5ProxyChain: !!( + socks5ProxyChain && (socks5ProxyChain as any).length > 0 + ), proxyChainLength: socks5ProxyChain ? (socks5ProxyChain as any).length : 0, }); // Check if SOCKS5 proxy is enabled (either single proxy or chain) - if (useSocks5 && (socks5Host || (socks5ProxyChain && (socks5ProxyChain as any).length > 0))) { + if ( + useSocks5 && + (socks5Host || (socks5ProxyChain && (socks5ProxyChain as any).length > 0)) + ) { fileLogger.info("SOCKS5 enabled for SFTP, creating connection", { operation: "sftp_socks5_enabled", sessionId, @@ -839,18 +844,14 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { }); try { - const socks5Socket = await createSocks5Connection( - ip, - port, - { - useSocks5, - socks5Host, - socks5Port, - socks5Username, - socks5Password, - socks5ProxyChain: socks5ProxyChain as any, - }, - ); + const socks5Socket = await createSocks5Connection(ip, port, { + useSocks5, + socks5Host, + socks5Port, + socks5Username, + socks5Password, + socks5ProxyChain: socks5ProxyChain as any, + }); if (socks5Socket) { fileLogger.info("SOCKS5 socket created for SFTP", { @@ -1545,7 +1546,22 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { const tryFallbackMethod = () => { try { - const base64Content = Buffer.from(content, "utf8").toString("base64"); + let contentBuffer: Buffer; + if (typeof content === "string") { + try { + contentBuffer = Buffer.from(content, "base64"); + if (contentBuffer.toString("base64") !== content) { + contentBuffer = Buffer.from(content, "utf8"); + } + } catch { + contentBuffer = Buffer.from(content, "utf8"); + } + } else if (Buffer.isBuffer(content)) { + contentBuffer = content; + } else { + contentBuffer = Buffer.from(content); + } + const base64Content = contentBuffer.toString("base64"); const escapedPath = filePath.replace(/'/g, "'\"'\"'"); const writeCommand = `echo '${base64Content}' | base64 -d > '${escapedPath}' && echo "SUCCESS"`; @@ -1746,7 +1762,22 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => { const tryFallbackMethod = () => { try { - const base64Content = Buffer.from(content, "utf8").toString("base64"); + let contentBuffer: Buffer; + if (typeof content === "string") { + try { + contentBuffer = Buffer.from(content, "base64"); + if (contentBuffer.toString("base64") !== content) { + contentBuffer = Buffer.from(content, "utf8"); + } + } catch { + contentBuffer = Buffer.from(content, "utf8"); + } + } else if (Buffer.isBuffer(content)) { + contentBuffer = content; + } else { + contentBuffer = Buffer.from(content); + } + const base64Content = contentBuffer.toString("base64"); const chunkSize = 1000000; const chunks = [];