Add support for passwordless host authentication
Allow adding SSH hosts without authentication credentials by introducing a new "none" auth type. This enables users to configure hosts for later use with SSH agent or manual credential addition. Changes: - Add authType "none" to type definitions - Update frontend form to support "None" authentication option - Skip credential validation for "none" auth type - Update backend to accept hosts without credentials - Add i18n support for English and Chinese Fixes #278
This commit is contained in:
@@ -281,7 +281,14 @@ router.post(
|
||||
sshDataObj.keyPassword = keyPassword || null;
|
||||
sshDataObj.keyType = keyType;
|
||||
sshDataObj.password = null;
|
||||
} else if (effectiveAuthType === "none") {
|
||||
// No authentication credentials - set all to null
|
||||
sshDataObj.password = null;
|
||||
sshDataObj.key = null;
|
||||
sshDataObj.keyPassword = null;
|
||||
sshDataObj.keyType = null;
|
||||
} else {
|
||||
// credential type or fallback - set all to null except credentialId
|
||||
sshDataObj.password = null;
|
||||
sshDataObj.key = null;
|
||||
sshDataObj.keyPassword = null;
|
||||
@@ -471,7 +478,14 @@ router.put(
|
||||
sshDataObj.keyType = keyType;
|
||||
}
|
||||
sshDataObj.password = null;
|
||||
} else if (effectiveAuthType === "none") {
|
||||
// No authentication credentials - set all to null
|
||||
sshDataObj.password = null;
|
||||
sshDataObj.key = null;
|
||||
sshDataObj.keyPassword = null;
|
||||
sshDataObj.keyType = null;
|
||||
} else {
|
||||
// credential type or fallback - set all to null except credentialId
|
||||
sshDataObj.password = null;
|
||||
sshDataObj.key = null;
|
||||
sshDataObj.keyPassword = null;
|
||||
@@ -1356,10 +1370,12 @@ router.post(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!["password", "key", "credential"].includes(hostData.authType)) {
|
||||
if (
|
||||
!["password", "key", "credential", "none"].includes(hostData.authType)
|
||||
) {
|
||||
results.failed++;
|
||||
results.errors.push(
|
||||
`Host ${i + 1}: Invalid authType. Must be 'password', 'key', or 'credential'`,
|
||||
`Host ${i + 1}: Invalid authType. Must be 'password', 'key', 'credential', or 'none'`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -1391,6 +1407,8 @@ router.post(
|
||||
continue;
|
||||
}
|
||||
|
||||
// "none" authType requires no validation - no credentials needed
|
||||
|
||||
const sshDataObj: any = {
|
||||
userId: userId,
|
||||
name: hostData.name || `${hostData.username}@${hostData.ip}`,
|
||||
|
||||
@@ -639,6 +639,8 @@
|
||||
"password": "Password",
|
||||
"key": "Key",
|
||||
"credential": "Credential",
|
||||
"none": "None",
|
||||
"noneDescription": "No authentication credentials required. You can add credentials later or use SSH agent for authentication.",
|
||||
"selectCredential": "Select Credential",
|
||||
"selectCredentialPlaceholder": "Choose a credential...",
|
||||
"credentialRequired": "Credential is required when using credential authentication",
|
||||
|
||||
@@ -635,6 +635,8 @@
|
||||
"password": "密码",
|
||||
"key": "密钥",
|
||||
"credential": "凭证",
|
||||
"none": "无",
|
||||
"noneDescription": "无需认证凭证。您可以稍后添加凭证或使用 SSH 代理进行认证。",
|
||||
"selectCredential": "选择凭证",
|
||||
"selectCredentialPlaceholder": "选择一个凭证...",
|
||||
"credentialRequired": "使用凭证认证时需要选择凭证",
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ export interface SSHHost {
|
||||
folder: string;
|
||||
tags: string[];
|
||||
pin: boolean;
|
||||
authType: "password" | "key" | "credential";
|
||||
authType: "password" | "key" | "credential" | "none";
|
||||
password?: string;
|
||||
key?: string;
|
||||
keyPassword?: string;
|
||||
@@ -47,7 +47,7 @@ export interface SSHHostData {
|
||||
folder?: string;
|
||||
tags?: string[];
|
||||
pin?: boolean;
|
||||
authType: "password" | "key" | "credential";
|
||||
authType: "password" | "key" | "credential" | "none";
|
||||
password?: string;
|
||||
key?: File | null;
|
||||
keyPassword?: string;
|
||||
|
||||
@@ -48,7 +48,7 @@ interface SSHHost {
|
||||
folder: string;
|
||||
tags: string[];
|
||||
pin: boolean;
|
||||
authType: string;
|
||||
authType: "password" | "key" | "credential" | "none";
|
||||
password?: string;
|
||||
key?: string;
|
||||
keyPassword?: string;
|
||||
@@ -79,9 +79,9 @@ export function HostManagerEditor({
|
||||
const [credentials, setCredentials] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [authTab, setAuthTab] = useState<"password" | "key" | "credential">(
|
||||
"password",
|
||||
);
|
||||
const [authTab, setAuthTab] = useState<
|
||||
"password" | "key" | "credential" | "none"
|
||||
>("password");
|
||||
const [keyInputMethod, setKeyInputMethod] = useState<"upload" | "paste">(
|
||||
"upload",
|
||||
);
|
||||
@@ -174,7 +174,7 @@ export function HostManagerEditor({
|
||||
folder: z.string().optional(),
|
||||
tags: z.array(z.string().min(1)).default([]),
|
||||
pin: z.boolean().default(false),
|
||||
authType: z.enum(["password", "key", "credential"]),
|
||||
authType: z.enum(["password", "key", "credential", "none"]),
|
||||
credentialId: z.number().optional().nullable(),
|
||||
password: z.string().optional(),
|
||||
key: z.any().optional().nullable(),
|
||||
@@ -210,6 +210,11 @@ export function HostManagerEditor({
|
||||
defaultPath: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
// Skip authentication validation for "none" type
|
||||
if (data.authType === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.authType === "key") {
|
||||
if (
|
||||
!data.key ||
|
||||
@@ -313,7 +318,9 @@ export function HostManagerEditor({
|
||||
? "credential"
|
||||
: cleanedHost.key
|
||||
? "key"
|
||||
: "password";
|
||||
: cleanedHost.password
|
||||
? "password"
|
||||
: "none";
|
||||
setAuthTab(defaultAuthType);
|
||||
|
||||
const formData = {
|
||||
@@ -324,7 +331,7 @@ export function HostManagerEditor({
|
||||
folder: cleanedHost.folder || "",
|
||||
tags: cleanedHost.tags || [],
|
||||
pin: Boolean(cleanedHost.pin),
|
||||
authType: defaultAuthType as "password" | "key" | "credential",
|
||||
authType: defaultAuthType as "password" | "key" | "credential" | "none",
|
||||
credentialId: null,
|
||||
password: "",
|
||||
key: null,
|
||||
@@ -863,7 +870,8 @@ export function HostManagerEditor({
|
||||
const newAuthType = value as
|
||||
| "password"
|
||||
| "key"
|
||||
| "credential";
|
||||
| "credential"
|
||||
| "none";
|
||||
setAuthTab(newAuthType);
|
||||
form.setValue("authType", newAuthType);
|
||||
|
||||
@@ -880,6 +888,13 @@ export function HostManagerEditor({
|
||||
form.setValue("key", null);
|
||||
form.setValue("keyPassword", "");
|
||||
form.setValue("keyType", "auto");
|
||||
} else if (newAuthType === "none") {
|
||||
// Clear all authentication fields for "none" type
|
||||
form.setValue("password", "");
|
||||
form.setValue("key", null);
|
||||
form.setValue("keyPassword", "");
|
||||
form.setValue("keyType", "auto");
|
||||
form.setValue("credentialId", null);
|
||||
}
|
||||
}}
|
||||
className="flex-1 flex flex-col h-full min-h-0"
|
||||
@@ -892,6 +907,7 @@ export function HostManagerEditor({
|
||||
<TabsTrigger value="credential">
|
||||
{t("hosts.credential")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="none">{t("hosts.none")}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="password">
|
||||
<FormField
|
||||
@@ -1110,6 +1126,13 @@ export function HostManagerEditor({
|
||||
)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="none">
|
||||
<FormItem>
|
||||
<FormDescription>
|
||||
{t("hosts.noneDescription")}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</TabsContent>
|
||||
<TabsContent value="terminal">
|
||||
|
||||
Reference in New Issue
Block a user