Code cleanup
This commit is contained in:
+8
-8
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ app.get("/version", authenticateJWT, async (req, res) => {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
for (const getVersion of versionSources) {
|
||||
|
||||
@@ -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<void>((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) => {
|
||||
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}`));
|
||||
rejectCmd(
|
||||
new Error(`mkdir command failed with code ${code}`),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on("data", (data) => {
|
||||
authLogger.info("mkdir command output", { host: hostConfig.ip, output: data.toString() });
|
||||
});
|
||||
});
|
||||
stream.on("data", (data) => {});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const keyExists = await new Promise<boolean>(
|
||||
(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<void>((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) {}
|
||||
|
||||
// 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 });
|
||||
const escapedKey = actualPublicKey
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/'/g, "'\\''");
|
||||
|
||||
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<boolean>(
|
||||
(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);
|
||||
});
|
||||
},
|
||||
@@ -1354,27 +1324,28 @@ async function deploySSHKeyToHost(
|
||||
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:
|
||||
|
||||
@@ -926,7 +926,9 @@ 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);
|
||||
|
||||
@@ -9,7 +9,6 @@ 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";
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -279,9 +275,17 @@ 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") ||
|
||||
@@ -309,14 +313,17 @@ 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", {
|
||||
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" });
|
||||
@@ -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];
|
||||
@@ -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];
|
||||
@@ -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];
|
||||
@@ -690,7 +692,6 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
|
||||
const { sessionId, path: filePath, content, hostId, userId } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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];
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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") ||
|
||||
|
||||
+10
-4
@@ -30,7 +30,9 @@ import { systemLogger, versionLogger } from "./utils/logger.js";
|
||||
() => {
|
||||
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) {
|
||||
|
||||
@@ -12,7 +12,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const now = Date.now();
|
||||
const lastToast = lastToastRef.current;
|
||||
|
||||
if (lastToast && lastToast.text === message && (now - lastToast.timestamp) < 1000) {
|
||||
if (
|
||||
lastToast &&
|
||||
lastToast.text === message &&
|
||||
now - lastToast.timestamp < 1000
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,10 +25,14 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
};
|
||||
|
||||
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' }),
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<any>(null);
|
||||
const [versionChecking, setVersionChecking] = useState(false);
|
||||
@@ -93,7 +97,6 @@ export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = fal
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (!versionInfo || versionDismissed) {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
@@ -131,10 +134,7 @@ export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = fal
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleContinue}
|
||||
className="flex-1 h-10"
|
||||
>
|
||||
<Button onClick={handleContinue} className="flex-1 h-10">
|
||||
{t("common.continue")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -177,10 +177,7 @@ export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = fal
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleContinue}
|
||||
className="flex-1 h-10"
|
||||
>
|
||||
<Button onClick={handleContinue} className="flex-1 h-10">
|
||||
{t("common.continue")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+13
-10
@@ -463,14 +463,19 @@ export let statsApi: AxiosInstance;
|
||||
export let authApi: AxiosInstance;
|
||||
|
||||
if (isElectron()) {
|
||||
getServerConfig().then((config) => {
|
||||
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);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"Failed to load server config, initializing with default:",
|
||||
error,
|
||||
);
|
||||
initializeApiInstances();
|
||||
});
|
||||
} else {
|
||||
@@ -536,14 +541,12 @@ function handleApiError(error: unknown, operation: string): never {
|
||||
errorContext,
|
||||
);
|
||||
|
||||
const isLoginEndpoint = url?.includes('/users/login');
|
||||
const errorMessage = isLoginEndpoint ? message : "Authentication required. Please log in again.";
|
||||
const isLoginEndpoint = url?.includes("/users/login");
|
||||
const errorMessage = isLoginEndpoint
|
||||
? message
|
||||
: "Authentication required. Please log in again.";
|
||||
|
||||
throw new ApiError(
|
||||
errorMessage,
|
||||
401,
|
||||
"AUTH_REQUIRED",
|
||||
);
|
||||
throw new ApiError(errorMessage, 401, "AUTH_REQUIRED");
|
||||
} else if (status === 403) {
|
||||
authLogger.warn(`Access denied: ${method} ${url}`, errorContext);
|
||||
throw new ApiError(
|
||||
|
||||
Reference in New Issue
Block a user