diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index 7e64d754..dd764c22 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -45,6 +45,9 @@ export const sshData = sqliteTable("ssh_data", { authType: text("auth_type").notNull(), password: text("password"), + requirePassword: integer("require_password", { mode: "boolean" }) + .notNull() + .default(true), key: text("key", { length: 8192 }), keyPassword: text("key_password"), keyType: text("key_type"), diff --git a/src/backend/database/routes/ssh.ts b/src/backend/database/routes/ssh.ts index f070cfaf..516ee924 100644 --- a/src/backend/database/routes/ssh.ts +++ b/src/backend/database/routes/ssh.ts @@ -77,6 +77,7 @@ router.get("/db/host/internal", async (req: Request, res: Response) => { : [] : [], pin: !!row.pin, + requirePassword: !!row.requirePassword, enableTerminal: !!row.enableTerminal, enableTunnel: !!row.enableTunnel, tunnelConnections: row.tunnelConnections @@ -137,6 +138,7 @@ router.post( port, username, password, + requirePassword, authMethod, authType, credentialId, @@ -188,6 +190,7 @@ router.post( if (effectiveAuthType === "password") { sshDataObj.password = password || null; + sshDataObj.requirePassword = requirePassword !== false ? 1 : 0; sshDataObj.key = null; sshDataObj.keyPassword = null; sshDataObj.keyType = null; @@ -196,6 +199,14 @@ router.post( sshDataObj.keyPassword = keyPassword || null; sshDataObj.keyType = keyType; sshDataObj.password = null; + sshDataObj.requirePassword = 1; // Default to true for non-password auth + } else { + // For credential auth + sshDataObj.password = null; + sshDataObj.key = null; + sshDataObj.keyPassword = null; + sshDataObj.keyType = null; + sshDataObj.requirePassword = 1; // Default to true for non-password auth } try { @@ -222,6 +233,7 @@ router.post( : [] : [], pin: !!createdHost.pin, + requirePassword: !!createdHost.requirePassword, enableTerminal: !!createdHost.enableTerminal, enableTunnel: !!createdHost.enableTunnel, tunnelConnections: createdHost.tunnelConnections @@ -308,6 +320,7 @@ router.put( port, username, password, + requirePassword, authMethod, authType, credentialId, @@ -362,6 +375,7 @@ router.put( if (password) { sshDataObj.password = password; } + sshDataObj.requirePassword = requirePassword !== false ? 1 : 0; sshDataObj.key = null; sshDataObj.keyPassword = null; sshDataObj.keyType = null; @@ -376,6 +390,14 @@ router.put( sshDataObj.keyType = keyType; } sshDataObj.password = null; + sshDataObj.requirePassword = 1; // Default to true for non-password auth + } else { + // For credential auth + sshDataObj.password = null; + sshDataObj.key = null; + sshDataObj.keyPassword = null; + sshDataObj.keyType = null; + sshDataObj.requirePassword = 1; // Default to true for non-password auth } try { @@ -408,6 +430,7 @@ router.put( : [] : [], pin: !!updatedHost.pin, + requirePassword: !!updatedHost.requirePassword, enableTerminal: !!updatedHost.enableTerminal, enableTunnel: !!updatedHost.enableTunnel, tunnelConnections: updatedHost.tunnelConnections @@ -475,6 +498,7 @@ router.get("/db/host", authenticateJWT, async (req: Request, res: Response) => { : [] : [], pin: !!row.pin, + requirePassword: !!row.requirePassword, enableTerminal: !!row.enableTerminal, enableTunnel: !!row.enableTunnel, tunnelConnections: row.tunnelConnections diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 2f6b049d..17449026 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -502,6 +502,8 @@ "upload": "Upload", "authentication": "Authentication", "password": "Password", + "requirePassword": "Require Password", + "requirePasswordDescription": "When disabled, sessions can be saved without entering a password", "key": "Key", "credential": "Credential", "selectCredential": "Select Credential", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 64be9056..1fbd14ee 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -502,6 +502,8 @@ "upload": "上传", "authentication": "认证方式", "password": "密码", + "requirePassword": "要求密码", + "requirePasswordDescription": "禁用时,可以在不输入密码的情况下保存会话", "key": "密钥", "credential": "凭证", "selectCredential": "选择凭证", diff --git a/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx b/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx index cad2565e..af3745f2 100644 --- a/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx +++ b/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx @@ -45,6 +45,7 @@ interface SSHHost { pin: boolean; authType: string; password?: string; + requirePassword?: boolean; key?: string; keyPassword?: string; keyType?: string; @@ -172,6 +173,7 @@ export function HostManagerEditor({ authType: z.enum(["password", "key", "credential"]), credentialId: z.number().optional().nullable(), password: z.string().optional(), + requirePassword: z.boolean().default(true), key: z.any().optional().nullable(), keyPassword: z.string().optional(), keyType: z @@ -206,7 +208,7 @@ export function HostManagerEditor({ }) .superRefine((data, ctx) => { if (data.authType === "password") { - if (!data.password || data.password.trim() === "") { + if (data.requirePassword && (!data.password || data.password.trim() === "")) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: t("hosts.passwordRequired"), @@ -274,6 +276,7 @@ export function HostManagerEditor({ authType: "password" as const, credentialId: null, password: "", + requirePassword: true, key: null, keyPassword: "", keyType: "auto" as const, @@ -330,6 +333,7 @@ export function HostManagerEditor({ authType: defaultAuthType as "password" | "key" | "credential", credentialId: null, password: "", + requirePassword: cleanedHost.requirePassword ?? true, key: null, keyPassword: "", keyType: "auto" as const, @@ -365,6 +369,7 @@ export function HostManagerEditor({ authType: "password" as const, credentialId: null, password: "", + requirePassword: true, key: null, keyPassword: "", keyType: "auto" as const, @@ -867,6 +872,24 @@ export function HostManagerEditor({ + ( + + {t("hosts.requirePassword")} + + + + + {t("hosts.requirePasswordDescription")} + + + )} + />