Fix SSH password authentication logic by removing requirePassword field
This commit eliminates the confusing requirePassword field that was causing authentication issues where users couldn't disable password requirements. Changes: - Remove requirePassword field from database schema and migrations - Simplify SSH authentication logic by removing special case branches - Update frontend to remove requirePassword UI controls - Clean up translation files to remove unused strings - Support standard SSH empty password authentication The new design follows the principle of "good taste" - password field itself now expresses the requirement: null/empty = no password auth, value = use password. Fixes the issue where setting requirePassword=false didn't work as expected. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -365,11 +365,6 @@ const migrateSchema = () => {
|
|||||||
"INTEGER REFERENCES ssh_credentials(id)",
|
"INTEGER REFERENCES ssh_credentials(id)",
|
||||||
);
|
);
|
||||||
|
|
||||||
addColumnIfNotExists(
|
|
||||||
"ssh_data",
|
|
||||||
"require_password",
|
|
||||||
"INTEGER NOT NULL DEFAULT 1",
|
|
||||||
);
|
|
||||||
|
|
||||||
// SSH credentials table migrations for encryption support
|
// SSH credentials table migrations for encryption support
|
||||||
addColumnIfNotExists("ssh_credentials", "private_key", "TEXT");
|
addColumnIfNotExists("ssh_credentials", "private_key", "TEXT");
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ export const sshData = sqliteTable("ssh_data", {
|
|||||||
authType: text("auth_type").notNull(),
|
authType: text("auth_type").notNull(),
|
||||||
|
|
||||||
password: text("password"),
|
password: text("password"),
|
||||||
requirePassword: integer("require_password", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(true),
|
|
||||||
key: text("key", { length: 8192 }),
|
key: text("key", { length: 8192 }),
|
||||||
keyPassword: text("key_password"),
|
keyPassword: text("key_password"),
|
||||||
keyType: text("key_type"),
|
keyType: text("key_type"),
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ router.get("/db/host/internal", async (req: Request, res: Response) => {
|
|||||||
: []
|
: []
|
||||||
: [],
|
: [],
|
||||||
pin: !!row.pin,
|
pin: !!row.pin,
|
||||||
requirePassword: !!row.requirePassword,
|
|
||||||
enableTerminal: !!row.enableTerminal,
|
enableTerminal: !!row.enableTerminal,
|
||||||
enableTunnel: !!row.enableTunnel,
|
enableTunnel: !!row.enableTunnel,
|
||||||
tunnelConnections: row.tunnelConnections
|
tunnelConnections: row.tunnelConnections
|
||||||
@@ -138,7 +137,6 @@ router.post(
|
|||||||
port,
|
port,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
requirePassword,
|
|
||||||
authMethod,
|
authMethod,
|
||||||
authType,
|
authType,
|
||||||
credentialId,
|
credentialId,
|
||||||
@@ -190,7 +188,6 @@ router.post(
|
|||||||
|
|
||||||
if (effectiveAuthType === "password") {
|
if (effectiveAuthType === "password") {
|
||||||
sshDataObj.password = password || null;
|
sshDataObj.password = password || null;
|
||||||
sshDataObj.requirePassword = requirePassword !== false ? 1 : 0;
|
|
||||||
sshDataObj.key = null;
|
sshDataObj.key = null;
|
||||||
sshDataObj.keyPassword = null;
|
sshDataObj.keyPassword = null;
|
||||||
sshDataObj.keyType = null;
|
sshDataObj.keyType = null;
|
||||||
@@ -199,14 +196,12 @@ router.post(
|
|||||||
sshDataObj.keyPassword = keyPassword || null;
|
sshDataObj.keyPassword = keyPassword || null;
|
||||||
sshDataObj.keyType = keyType;
|
sshDataObj.keyType = keyType;
|
||||||
sshDataObj.password = null;
|
sshDataObj.password = null;
|
||||||
sshDataObj.requirePassword = 1; // Default to true for non-password auth
|
|
||||||
} else {
|
} else {
|
||||||
// For credential auth
|
// For credential auth
|
||||||
sshDataObj.password = null;
|
sshDataObj.password = null;
|
||||||
sshDataObj.key = null;
|
sshDataObj.key = null;
|
||||||
sshDataObj.keyPassword = null;
|
sshDataObj.keyPassword = null;
|
||||||
sshDataObj.keyType = null;
|
sshDataObj.keyType = null;
|
||||||
sshDataObj.requirePassword = 1; // Default to true for non-password auth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -237,7 +232,6 @@ router.post(
|
|||||||
: []
|
: []
|
||||||
: [],
|
: [],
|
||||||
pin: !!createdHost.pin,
|
pin: !!createdHost.pin,
|
||||||
requirePassword: !!createdHost.requirePassword,
|
|
||||||
enableTerminal: !!createdHost.enableTerminal,
|
enableTerminal: !!createdHost.enableTerminal,
|
||||||
enableTunnel: !!createdHost.enableTunnel,
|
enableTunnel: !!createdHost.enableTunnel,
|
||||||
tunnelConnections: createdHost.tunnelConnections
|
tunnelConnections: createdHost.tunnelConnections
|
||||||
@@ -324,7 +318,6 @@ router.put(
|
|||||||
port,
|
port,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
requirePassword,
|
|
||||||
authMethod,
|
authMethod,
|
||||||
authType,
|
authType,
|
||||||
credentialId,
|
credentialId,
|
||||||
@@ -379,7 +372,6 @@ router.put(
|
|||||||
if (password) {
|
if (password) {
|
||||||
sshDataObj.password = password;
|
sshDataObj.password = password;
|
||||||
}
|
}
|
||||||
sshDataObj.requirePassword = requirePassword !== false ? 1 : 0;
|
|
||||||
sshDataObj.key = null;
|
sshDataObj.key = null;
|
||||||
sshDataObj.keyPassword = null;
|
sshDataObj.keyPassword = null;
|
||||||
sshDataObj.keyType = null;
|
sshDataObj.keyType = null;
|
||||||
@@ -394,14 +386,12 @@ router.put(
|
|||||||
sshDataObj.keyType = keyType;
|
sshDataObj.keyType = keyType;
|
||||||
}
|
}
|
||||||
sshDataObj.password = null;
|
sshDataObj.password = null;
|
||||||
sshDataObj.requirePassword = 1; // Default to true for non-password auth
|
|
||||||
} else {
|
} else {
|
||||||
// For credential auth
|
// For credential auth
|
||||||
sshDataObj.password = null;
|
sshDataObj.password = null;
|
||||||
sshDataObj.key = null;
|
sshDataObj.key = null;
|
||||||
sshDataObj.keyPassword = null;
|
sshDataObj.keyPassword = null;
|
||||||
sshDataObj.keyType = null;
|
sshDataObj.keyType = null;
|
||||||
sshDataObj.requirePassword = 1; // Default to true for non-password auth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -441,7 +431,6 @@ router.put(
|
|||||||
: []
|
: []
|
||||||
: [],
|
: [],
|
||||||
pin: !!updatedHost.pin,
|
pin: !!updatedHost.pin,
|
||||||
requirePassword: !!updatedHost.requirePassword,
|
|
||||||
enableTerminal: !!updatedHost.enableTerminal,
|
enableTerminal: !!updatedHost.enableTerminal,
|
||||||
enableTunnel: !!updatedHost.enableTunnel,
|
enableTunnel: !!updatedHost.enableTunnel,
|
||||||
tunnelConnections: updatedHost.tunnelConnections
|
tunnelConnections: updatedHost.tunnelConnections
|
||||||
@@ -509,7 +498,6 @@ router.get("/db/host", authenticateJWT, async (req: Request, res: Response) => {
|
|||||||
: []
|
: []
|
||||||
: [],
|
: [],
|
||||||
pin: !!row.pin,
|
pin: !!row.pin,
|
||||||
requirePassword: !!row.requirePassword,
|
|
||||||
enableTerminal: !!row.enableTerminal,
|
enableTerminal: !!row.enableTerminal,
|
||||||
enableTunnel: !!row.enableTunnel,
|
enableTunnel: !!row.enableTunnel,
|
||||||
tunnelConnections: row.tunnelConnections
|
tunnelConnections: row.tunnelConnections
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ class DatabaseSQLiteExport {
|
|||||||
pin INTEGER NOT NULL DEFAULT 0,
|
pin INTEGER NOT NULL DEFAULT 0,
|
||||||
auth_type TEXT NOT NULL,
|
auth_type TEXT NOT NULL,
|
||||||
password TEXT,
|
password TEXT,
|
||||||
require_password INTEGER NOT NULL DEFAULT 1,
|
|
||||||
key TEXT,
|
key TEXT,
|
||||||
key_password TEXT,
|
key_password TEXT,
|
||||||
key_type TEXT,
|
key_type TEXT,
|
||||||
@@ -225,7 +224,6 @@ class DatabaseSQLiteExport {
|
|||||||
const fieldMappings: Record<string, string> = {
|
const fieldMappings: Record<string, string> = {
|
||||||
userId: "user_id",
|
userId: "user_id",
|
||||||
authType: "auth_type",
|
authType: "auth_type",
|
||||||
requirePassword: "require_password",
|
|
||||||
keyPassword: "key_password",
|
keyPassword: "key_password",
|
||||||
keyType: "key_type",
|
keyType: "key_type",
|
||||||
credentialId: "credential_id",
|
credentialId: "credential_id",
|
||||||
@@ -464,7 +462,6 @@ class DatabaseSQLiteExport {
|
|||||||
const columnToFieldMappings: Record<string, string> = {
|
const columnToFieldMappings: Record<string, string> = {
|
||||||
user_id: "userId",
|
user_id: "userId",
|
||||||
auth_type: "authType",
|
auth_type: "authType",
|
||||||
require_password: "requirePassword",
|
|
||||||
key_password: "keyPassword",
|
key_password: "keyPassword",
|
||||||
key_type: "keyType",
|
key_type: "keyType",
|
||||||
credential_id: "credentialId",
|
credential_id: "credentialId",
|
||||||
|
|||||||
@@ -576,8 +576,6 @@
|
|||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"authentication": "Authentication",
|
"authentication": "Authentication",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"requirePassword": "Require Password",
|
|
||||||
"requirePasswordDescription": "When disabled, sessions can be saved without entering a password",
|
|
||||||
"key": "Key",
|
"key": "Key",
|
||||||
"credential": "Credential",
|
"credential": "Credential",
|
||||||
"selectCredential": "Select Credential",
|
"selectCredential": "Select Credential",
|
||||||
|
|||||||
@@ -576,8 +576,6 @@
|
|||||||
"upload": "上传",
|
"upload": "上传",
|
||||||
"authentication": "认证方式",
|
"authentication": "认证方式",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"requirePassword": "要求密码",
|
|
||||||
"requirePasswordDescription": "禁用时,可以在不输入密码的情况下保存会话",
|
|
||||||
"key": "密钥",
|
"key": "密钥",
|
||||||
"credential": "凭证",
|
"credential": "凭证",
|
||||||
"selectCredential": "选择凭证",
|
"selectCredential": "选择凭证",
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ interface SSHHost {
|
|||||||
pin: boolean;
|
pin: boolean;
|
||||||
authType: string;
|
authType: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
requirePassword?: boolean;
|
|
||||||
key?: string;
|
key?: string;
|
||||||
keyPassword?: string;
|
keyPassword?: string;
|
||||||
keyType?: string;
|
keyType?: string;
|
||||||
@@ -173,7 +172,6 @@ export function HostManagerEditor({
|
|||||||
authType: z.enum(["password", "key", "credential"]),
|
authType: z.enum(["password", "key", "credential"]),
|
||||||
credentialId: z.number().optional().nullable(),
|
credentialId: z.number().optional().nullable(),
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
requirePassword: z.boolean().default(true),
|
|
||||||
key: z.any().optional().nullable(),
|
key: z.any().optional().nullable(),
|
||||||
keyPassword: z.string().optional(),
|
keyPassword: z.string().optional(),
|
||||||
keyType: z
|
keyType: z
|
||||||
@@ -207,18 +205,7 @@ export function HostManagerEditor({
|
|||||||
defaultPath: z.string().optional(),
|
defaultPath: z.string().optional(),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.authType === "password") {
|
if (data.authType === "key") {
|
||||||
if (
|
|
||||||
data.requirePassword &&
|
|
||||||
(!data.password || data.password.trim() === "")
|
|
||||||
) {
|
|
||||||
ctx.addIssue({
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
message: t("hosts.passwordRequired"),
|
|
||||||
path: ["password"],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (data.authType === "key") {
|
|
||||||
if (
|
if (
|
||||||
!data.key ||
|
!data.key ||
|
||||||
(typeof data.key === "string" && data.key.trim() === "")
|
(typeof data.key === "string" && data.key.trim() === "")
|
||||||
@@ -279,7 +266,6 @@ export function HostManagerEditor({
|
|||||||
authType: "password" as const,
|
authType: "password" as const,
|
||||||
credentialId: null,
|
credentialId: null,
|
||||||
password: "",
|
password: "",
|
||||||
requirePassword: true,
|
|
||||||
key: null,
|
key: null,
|
||||||
keyPassword: "",
|
keyPassword: "",
|
||||||
keyType: "auto" as const,
|
keyType: "auto" as const,
|
||||||
@@ -336,7 +322,6 @@ export function HostManagerEditor({
|
|||||||
authType: defaultAuthType as "password" | "key" | "credential",
|
authType: defaultAuthType as "password" | "key" | "credential",
|
||||||
credentialId: null,
|
credentialId: null,
|
||||||
password: "",
|
password: "",
|
||||||
requirePassword: cleanedHost.requirePassword ?? true,
|
|
||||||
key: null,
|
key: null,
|
||||||
keyPassword: "",
|
keyPassword: "",
|
||||||
keyType: "auto" as const,
|
keyType: "auto" as const,
|
||||||
@@ -372,7 +357,6 @@ export function HostManagerEditor({
|
|||||||
authType: "password" as const,
|
authType: "password" as const,
|
||||||
credentialId: null,
|
credentialId: null,
|
||||||
password: "",
|
password: "",
|
||||||
requirePassword: true,
|
|
||||||
key: null,
|
key: null,
|
||||||
keyPassword: "",
|
keyPassword: "",
|
||||||
keyType: "auto" as const,
|
keyType: "auto" as const,
|
||||||
@@ -879,24 +863,6 @@ export function HostManagerEditor({
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="password">
|
<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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="password"
|
name="password"
|
||||||
@@ -906,7 +872,6 @@ export function HostManagerEditor({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
placeholder={t("placeholders.password")}
|
placeholder={t("placeholders.password")}
|
||||||
disabled={!form.watch("requirePassword")}
|
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user