v1.8.0 #429

Merged
LukeGus merged 198 commits from dev-1.8.0 into main 2025-11-05 16:36:16 +00:00
27 changed files with 114 additions and 48 deletions
Showing only changes of commit a81ab8e7ef - Show all commits

View File

@@ -403,7 +403,6 @@ jobs:
- name: Build macOS DMG
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
@@ -412,6 +411,7 @@ jobs:
if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then
npm run build
fi
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
npx electron-builder --mac dmg --universal --x64 --arm64 --publish never
- name: List release directory

View File

@@ -1039,26 +1039,19 @@ router.post("/logout", authenticateJWT, async (req, res) => {
// Route: Get current user's info using JWT
// GET /users/me
router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
console.log("=== /users/me CALLED ===");
const userId = (req as AuthenticatedRequest).userId;
console.log("User ID from JWT:", userId);
if (!isNonEmptyString(userId)) {
console.log("ERROR: Invalid userId");
authLogger.warn("Invalid userId in JWT for /users/me");
return res.status(401).json({ error: "Invalid userId" });
}
try {
const user = await db.select().from(users).where(eq(users.id, userId));
console.log("User found:", user.length > 0 ? "YES" : "NO");
if (!user || user.length === 0) {
console.log("ERROR: User not found in database");
authLogger.warn(`User not found for /users/me: ${userId}`);
return res.status(401).json({ error: "User not found" });
}
console.log("SUCCESS: Returning user info");
res.json({
userId: user[0].id,
username: user[0].username,
@@ -1067,7 +1060,6 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
totp_enabled: !!user[0].totp_enabled,
});
} catch (err) {
console.log("ERROR: Exception thrown:", err);
authLogger.error("Failed to get username", err);
res.status(500).json({ error: "Failed to get username" });
}

View File

@@ -459,6 +459,18 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
"The server does not support keyboard-interactive authentication. Please provide credentials.",
reason: "no_keyboard",
});
} else if (
resolvedCredentials.authType === "none" &&
(err.message.includes("All configured authentication methods failed") ||
err.message.includes("No supported authentication methods available") ||
err.message.includes("authentication methods failed"))
) {
res.status(200).json({
status: "auth_required",
message:
"The server does not support keyboard-interactive authentication. Please provide credentials.",
reason: "no_keyboard",
});
} else {
fileLogger.error("SSH connection failed for file manager", {
operation: "file_connect",

View File

@@ -485,6 +485,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
}, 60000);
let resolvedCredentials = { password, key, keyPassword, keyType, authType };
let authMethodNotAvailable = false;
if (credentialId && id && hostConfig.userId) {
try {
const credentials = await SimpleDBOps.select(
@@ -707,6 +708,23 @@ wss.on("connection", async (ws: WebSocket, req) => {
sshConn.on("error", (err: Error) => {
clearTimeout(connectionTimeout);
if (
(authMethodNotAvailable && resolvedCredentials.authType === "none") ||
(resolvedCredentials.authType === "none" &&
err.message.includes("All configured authentication methods failed"))
) {
ws.send(
JSON.stringify({
type: "auth_method_not_available",
message:
"The server does not support keyboard-interactive authentication. Please provide credentials.",
}),
);
cleanupSSH(connectionTimeout);
return;
}
sshLogger.error("SSH connection error", err, {
operation: "ssh_connect",
hostId: id,
@@ -880,7 +898,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
host: ip,
port,
username,
tryKeyboard: true,
tryKeyboard: resolvedCredentials.authType === "none",
keepaliveInterval: 30000,
keepaliveCountMax: 3,
readyTimeout: 60000,
@@ -955,14 +973,7 @@ wss.on("connection", async (ws: WebSocket, req) => {
if (methodsLeft.includes("keyboard-interactive")) {
callback("keyboard-interactive");
} else {
ws.send(
JSON.stringify({
type: "auth_method_not_available",
message:
"The server does not support keyboard-interactive authentication. Please provide credentials.",
methodsAvailable: methodsLeft,
}),
);
authMethodNotAvailable = true;
callback(false);
}
} else {

View File

@@ -311,6 +311,8 @@
"next": "Weiter",
"previous": "Vorherige",
"refresh": "Aktualisieren",
"connect": "Verbinden",
"connecting": "Verbinde...",
"settings": "Einstellungen",
"profile": "Profil",
"help": "Hilfe",
@@ -1173,7 +1175,17 @@
"passwordResetSuccess": "Erfolgreich!",
"passwordResetSuccessDesc": "Ihr Passwort wurde erfolgreich zurückgesetzt! Sie können sich jetzt mit Ihrem neuen Passwort anmelden.",
"signUp": "Registrierung",
"dataLossWarning": "Wenn Sie Ihr Passwort auf diese Weise zurücksetzen, werden alle Ihre gespeicherten SSH-Hosts, Anmeldeinformationen und andere verschlüsselte Daten gelöscht. Diese Aktion kann nicht rückgängig gemacht werden. Verwenden Sie diese Option nur, wenn Sie Ihr Passwort vergessen haben und nicht angemeldet sind."
"dataLossWarning": "Wenn Sie Ihr Passwort auf diese Weise zurücksetzen, werden alle Ihre gespeicherten SSH-Hosts, Anmeldeinformationen und andere verschlüsselte Daten gelöscht. Diese Aktion kann nicht rückgängig gemacht werden. Verwenden Sie diese Option nur, wenn Sie Ihr Passwort vergessen haben und nicht angemeldet sind.",
"sshAuthenticationRequired": "SSH-Authentifizierung erforderlich",
"sshNoKeyboardInteractive": "Keyboard-Interactive-Authentifizierung nicht verfügbar",
"sshAuthenticationFailed": "Authentifizierung fehlgeschlagen",
"sshAuthenticationTimeout": "Authentifizierungs-Timeout",
"sshNoKeyboardInteractiveDescription": "Der Server unterstützt keine Keyboard-Interactive-Authentifizierung. Bitte geben Sie Ihr Passwort oder Ihren SSH-Schlüssel ein.",
"sshAuthFailedDescription": "Die angegebenen Anmeldeinformationen waren falsch. Bitte versuchen Sie es erneut mit gültigen Anmeldeinformationen.",
"sshTimeoutDescription": "Der Authentifizierungsversuch ist abgelaufen. Bitte versuchen Sie es erneut.",
"sshProvideCredentialsDescription": "Bitte geben Sie Ihre SSH-Anmeldeinformationen ein, um eine Verbindung zu diesem Server herzustellen.",
"sshPasswordDescription": "Geben Sie das Passwort für diese SSH-Verbindung ein.",
"sshKeyPasswordDescription": "Wenn Ihr SSH-Schlüssel verschlüsselt ist, geben Sie hier die Passphrase ein."
},
"errors": {
"notFound": "Seite nicht gefunden",

View File

@@ -54,7 +54,7 @@
"sshPrivateKey": "SSH Private Key",
"upload": "Upload",
"updateKey": "Update Key",
"keyPassword": "Key Password (optional)",
"keyPassword": "Key Password",
"keyType": "Key Type",
"keyTypeRSA": "RSA",
"keyTypeECDSA": "ECDSA",
@@ -287,6 +287,8 @@
"loading": "Loading",
"required": "Required",
"optional": "Optional",
"connect": "Connect",
"connecting": "Connecting...",
"clear": "Clear",
"toggleSidebar": "Toggle Sidebar",
"sidebar": "Sidebar",
@@ -778,7 +780,7 @@
"terminalCustomizationNotice": "Note: Terminal customizations only work on desktop (website and Electron app). Mobile apps and mobile website use system default terminal settings.",
"noneAuthTitle": "Keyboard-Interactive Authentication",
"noneAuthDescription": "This authentication method will use keyboard-interactive authentication when connecting to the SSH server.",
"noneAuthDetails": "Keyboard-interactive authentication allows the server to prompt you for credentials during connection. This is useful for servers that require multi-factor authentication or dynamic password entry."
"noneAuthDetails": "Keyboard-interactive authentication allows the server to prompt you for credentials during connection. This is useful for servers that require multi-factor authentication or if you do not want to save credentials locally."
},
"terminal": {
"title": "Terminal",
@@ -1275,6 +1277,16 @@
"yourBackupCodes": "Your Backup Codes",
"download": "Download",
"setupTwoFactorTitle": "Set Up Two-Factor Authentication",
"sshAuthenticationRequired": "SSH Authentication Required",
"sshNoKeyboardInteractive": "Keyboard-Interactive Authentication Unavailable",
"sshAuthenticationFailed": "Authentication Failed",
"sshAuthenticationTimeout": "Authentication Timeout",
"sshNoKeyboardInteractiveDescription": "The server does not support keyboard-interactive authentication. Please provide your password or SSH key.",
"sshAuthFailedDescription": "The provided credentials were incorrect. Please try again with valid credentials.",
"sshTimeoutDescription": "The authentication attempt timed out. Please try again.",
"sshProvideCredentialsDescription": "Please provide your SSH credentials to connect to this server.",
"sshPasswordDescription": "Enter the password for this SSH connection.",
"sshKeyPasswordDescription": "If your SSH key is encrypted, enter the passphrase here.",
"step1ScanQR": "Step 1: Scan the QR code with your authenticator app",
"manualEntryCode": "Manual Entry Code",
"cannotScanQRText": "If you can't scan the QR code, enter this code manually in your authenticator app",

View File

@@ -326,6 +326,8 @@
"next": "Próximo",
"previous": "Anterior",
"refresh": "Atualizar",
"connect": "Conectar",
"connecting": "Conectando...",
"settings": "Configurações",
"profile": "Perfil",
"help": "Ajuda",
@@ -1220,7 +1222,17 @@
"enterNewPassword": "Digite sua nova senha para o usuário:",
"passwordResetSuccess": "Sucesso!",
"signUp": "Cadastrar",
"dataLossWarning": "Redefinir sua senha desta forma excluirá todos os seus hosts SSH salvos, credenciais e outros dados criptografados. Esta ação não pode ser desfeita. Use isso apenas se você esqueceu sua senha e não está logado."
"dataLossWarning": "Redefinir sua senha desta forma excluirá todos os seus hosts SSH salvos, credenciais e outros dados criptografados. Esta ação não pode ser desfeita. Use isso apenas se você esqueceu sua senha e não está logado.",
"sshAuthenticationRequired": "Autenticação SSH Necessária",
"sshNoKeyboardInteractive": "Autenticação Interativa por Teclado Indisponível",
"sshAuthenticationFailed": "Falha na Autenticação",
"sshAuthenticationTimeout": "Tempo Limite de Autenticação",
"sshNoKeyboardInteractiveDescription": "O servidor não suporta autenticação interativa por teclado. Por favor, forneça sua senha ou chave SSH.",
"sshAuthFailedDescription": "As credenciais fornecidas estavam incorretas. Por favor, tente novamente com credenciais válidas.",
"sshTimeoutDescription": "A tentativa de autenticação expirou. Por favor, tente novamente.",
"sshProvideCredentialsDescription": "Por favor, forneça suas credenciais SSH para conectar a este servidor.",
"sshPasswordDescription": "Digite a senha para esta conexão SSH.",
"sshKeyPasswordDescription": "Se sua chave SSH estiver criptografada, digite a senha aqui."
},
"errors": {
"notFound": "Página não encontrada",

View File

@@ -345,6 +345,8 @@
"settingUp": "设置中...",
"next": "下一步",
"previous": "上一步",
"connect": "连接",
"connecting": "连接中...",
"refresh": "刷新",
"settings": "设置",
"profile": "个人资料",
@@ -1280,7 +1282,17 @@
"passwordResetSuccess": "成功!",
"passwordResetSuccessDesc": "您的密码已成功重置!您现在可以使用新密码登录。",
"signUp": "注册",
"dataLossWarning": "以这种方式重置密码将删除所有已保存的 SSH 主机、凭据和其他加密数据。此操作无法撤销。仅当您忘记密码且未登录时才使用此功能。"
"dataLossWarning": "以这种方式重置密码将删除所有已保存的 SSH 主机、凭据和其他加密数据。此操作无法撤销。仅当您忘记密码且未登录时才使用此功能。",
"sshAuthenticationRequired": "需要 SSH 身份验证",
"sshNoKeyboardInteractive": "键盘交互式身份验证不可用",
"sshAuthenticationFailed": "身份验证失败",
"sshAuthenticationTimeout": "身份验证超时",
"sshNoKeyboardInteractiveDescription": "服务器不支持键盘交互式身份验证。请提供您的密码或 SSH 密钥。",
"sshAuthFailedDescription": "提供的凭据不正确。请使用有效凭据重试。",
"sshTimeoutDescription": "身份验证尝试超时。请重试。",
"sshProvideCredentialsDescription": "请提供您的 SSH 凭据以连接到此服务器。",
"sshPasswordDescription": "输入此 SSH 连接的密码。",
"sshKeyPasswordDescription": "如果您的 SSH 密钥已加密,请在此处输入密码。"
},
"errors": {
"notFound": "页面未找到",

View File

@@ -250,6 +250,9 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
data: {
cols: terminal.cols,
rows: terminal.rows,
password: credentials.password,
sshKey: credentials.sshKey,
keyPassword: credentials.keyPassword,
hostConfig: {
...hostConfig,
password: credentials.password,
@@ -525,26 +528,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
} else if (msg.type === "error") {
const errorMessage = msg.message || t("terminal.unknownError");
if (
errorMessage.toLowerCase().includes("auth") ||
errorMessage.toLowerCase().includes("password") ||
errorMessage.toLowerCase().includes("permission") ||
errorMessage.toLowerCase().includes("denied") ||
errorMessage.toLowerCase().includes("invalid") ||
errorMessage.toLowerCase().includes("failed") ||
errorMessage.toLowerCase().includes("incorrect")
) {
toast.error(t("terminal.authError", { message: errorMessage }));
shouldNotReconnectRef.current = true;
if (webSocketRef.current) {
webSocketRef.current.close();
}
if (onClose) {
onClose();
}
return;
}
if (
errorMessage.toLowerCase().includes("connection") ||
errorMessage.toLowerCase().includes("timeout") ||
@@ -563,6 +546,26 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return;
}
if (
(errorMessage.toLowerCase().includes("auth") &&
errorMessage.toLowerCase().includes("failed")) ||
errorMessage.toLowerCase().includes("permission denied") ||
(errorMessage.toLowerCase().includes("invalid") &&
(errorMessage.toLowerCase().includes("password") ||
errorMessage.toLowerCase().includes("key"))) ||
errorMessage.toLowerCase().includes("incorrect password")
) {
toast.error(t("terminal.authError", { message: errorMessage }));
shouldNotReconnectRef.current = true;
if (webSocketRef.current) {
webSocketRef.current.close();
}
if (onClose) {
onClose();
}
return;
}
toast.error(t("terminal.error", { message: errorMessage }));
} else if (msg.type === "connected") {
setIsConnected(true);

View File

@@ -25,7 +25,7 @@ interface SshToolsSidebarProps {
onClose: () => void;
}
export function SshToolsSidebar({
export function SSHToolsSidebar({
isOpen,
onClose,
}: SshToolsSidebarProps): React.ReactElement | null {

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState, useMemo } from "react";
import { Terminal } from "@/ui/desktop/apps/terminal/Terminal.tsx";
import { Server as ServerView } from "@/ui/desktop/apps/server/Server.tsx";
import { FileManager } from "@/ui/desktop/apps/file manager/FileManager.tsx";
import { FileManager } from "@/ui/desktop/apps/file-manager/FileManager.tsx";
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
import {
ResizablePanelGroup,

View File

@@ -137,7 +137,7 @@ export function SSHAuthDialog({
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
className="absolute inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
style={{ backgroundColor: `${backgroundColor}dd` }}
>
<Card className="w-full max-w-2xl mx-4 shadow-2xl">

View File

@@ -8,7 +8,7 @@ import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
import { useTranslation } from "react-i18next";
import { TabDropdown } from "@/ui/desktop/navigation/tabs/TabDropdown.tsx";
import { SnippetsSidebar } from "@/ui/desktop/apps/terminal/SnippetsSidebar.tsx";
import { SshToolsSidebar } from "@/ui/desktop/apps/tools/SshToolsSidebar.tsx";
import { SSHToolsSidebar } from "@/ui/desktop/apps/tools/SSHToolsSidebar.tsx";
import { ToolsMenu } from "@/ui/desktop/apps/tools/ToolsMenu.tsx";
interface TabData {
@@ -497,7 +497,7 @@ export function TopNavbar({
</div>
)}
<SshToolsSidebar
<SSHToolsSidebar
isOpen={toolsSheetOpen}
onClose={() => setToolsSheetOpen(false)}
/>