Add optional password requirement for SSH sessions (Issue #118)
Users can now choose whether to require a password when saving SSH sessions. A new "Require Password" toggle has been added to the password authentication tab, allowing sessions to be saved without entering a password when disabled. - Add requirePassword boolean field to SSH host schema (defaults to true) - Update form validation to conditionally require password based on setting - Add "Require Password" toggle with description in Host Manager UI - Update all backend SSH routes to handle requirePassword field correctly - Add translations for new UI elements in English and Chinese - Maintain full backward compatibility with existing hosts Resolves #118
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -502,6 +502,8 @@
|
||||
"upload": "上传",
|
||||
"authentication": "认证方式",
|
||||
"password": "密码",
|
||||
"requirePassword": "要求密码",
|
||||
"requirePasswordDescription": "禁用时,可以在不输入密码的情况下保存会话",
|
||||
"key": "密钥",
|
||||
"credential": "凭证",
|
||||
"selectCredential": "选择凭证",
|
||||
|
||||
@@ -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({
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="password">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="requirePassword"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mb-4">
|
||||
<FormLabel>{t("hosts.requirePassword")}</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t("hosts.requirePasswordDescription")}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
@@ -876,6 +899,7 @@ export function HostManagerEditor({
|
||||
<FormControl>
|
||||
<PasswordInput
|
||||
placeholder={t("placeholders.password")}
|
||||
disabled={!form.watch("requirePassword")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
Reference in New Issue
Block a user