feat: store rdp, vnc and telnet logins in a generic per-protocol table

This commit is contained in:
LukeGus committed 2026-09-29 12:33:09 -05:00
1 parent e0c30a4e6f
commit bba06f7ff6
107 files changed
+18750 -1224

No files matched your search

+38 -45
View File
@@ -1,8 +1,9 @@
import type { AuthOverrideProtocol } from "../../../types/auth-protocols.js";
import {
parseSshOptions,
type HostSshOptions,
} from "../../hosts/ssh-options.js";
import { listHostProtocols } from "../../hosts/protocol-auth/registry.js";
import { sanitizeProtocolAuthForRecipient } from "../../hosts/protocol-auth/summary.js";
export function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
@@ -41,6 +42,10 @@ export function normalizeProtocolEnableFields(
);
}
/**
* The owner's SSH authentication, which a shared editor may not change. A
* plugin protocol's login is guarded in writeProtocolAuth instead.
*/
export const OWNER_PRIVATE_AUTH_FIELDS = {
ssh: [
"authType",
@@ -54,21 +59,7 @@ export const OWNER_PRIVATE_AUTH_FIELDS = {
"keyType",
"sudoPassword",
],
rdp: [
"rdpAuthType",
"rdpCredentialId",
"rdpUser",
"rdpPassword",
"rdpDomain",
],
vnc: ["vncAuthType", "vncCredentialId", "vncUser", "vncPassword"],
telnet: [
"telnetAuthType",
"telnetCredentialId",
"telnetUser",
"telnetPassword",
],
} as const satisfies Record<AuthOverrideProtocol, readonly string[]>;
} as const;
export const OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS = [
"sudoPassword",
@@ -80,7 +71,7 @@ export const OWNER_PRIVATE_SSH_OPTION_FIELDS = ["agentSocketPath"] as const;
export function containsOwnerPrivateAuthUpdate(
hostData: Record<string, unknown>,
protocol: AuthOverrideProtocol,
protocol: keyof typeof OWNER_PRIVATE_AUTH_FIELDS,
): boolean {
return OWNER_PRIVATE_AUTH_FIELDS[protocol].some((field) =>
Object.prototype.hasOwnProperty.call(hostData, field),
@@ -193,34 +184,41 @@ export type NormalizedImportedHost = Record<string, unknown> & {
socks5ProxyChain?: unknown;
portKnockSequence?: unknown;
overrideCredentialUsername?: unknown;
domain?: unknown;
enableSsh: boolean;
};
function legacyPrefix(protocolId: string): string {
return protocolId.replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase());
}
function legacyKey(protocolId: string): string {
const prefix = legacyPrefix(protocolId);
return prefix.charAt(0).toUpperCase() + prefix.slice(1);
}
export function normalizeImportedHost(
hostData: Record<string, unknown>,
): NormalizedImportedHost {
const credentialAlias =
asString(hostData.credentialAlias) || asString(hostData.credentialName);
// A row with no connectionType names its protocol the way a 2.8 export
// did: an "enable<Protocol>" flag and a "<protocol>Port".
const flagged = listHostProtocols().find((protocol) =>
asBoolean(hostData[`enable${legacyKey(protocol.id)}`]),
);
const connectionType =
asString(hostData.connectionType) ||
(asBoolean(hostData.enableRdp)
? "rdp"
: asBoolean(hostData.enableVnc)
? "vnc"
: asBoolean(hostData.enableTelnet)
? "telnet"
: "ssh");
asString(hostData.connectionType) || flagged?.id || "ssh";
const protocol = listHostProtocols().find(
(entry) => entry.id === connectionType,
);
const port =
asPort(hostData.port) ||
(connectionType === "rdp"
? asPort(hostData.rdpPort) || 3389
: connectionType === "vnc"
? asPort(hostData.vncPort) || 5900
: connectionType === "telnet"
? asPort(hostData.telnetPort) || 23
: asPort(hostData.sshPort) || 22);
(protocol
? asPort(hostData[`${legacyPrefix(protocol.id)}Port`]) ||
protocol.defaultPort ||
22
: asPort(hostData.sshPort) || 22);
return {
...hostData,
@@ -260,9 +258,6 @@ const SENSITIVE_FIELDS = [
"password",
"sudoPassword",
"socks5Password",
"rdpPassword",
"vncPassword",
"telnetPassword",
"autostartPassword",
];
@@ -282,9 +277,6 @@ export function stripSensitiveFields(
// 2.8 editors kept the sudo password inside terminal_config.
result.hasSudoPassword =
!!host.sudoPassword || !!terminalConfigForSudo?.sudoPassword;
result.hasRdpPassword = !!host.rdpPassword;
result.hasVncPassword = !!host.vncPassword;
result.hasTelnetPassword = !!host.telnetPassword;
for (const field of SENSITIVE_FIELDS) {
delete result[field];
}
@@ -326,7 +318,7 @@ const CONNECT_LEVEL_FIELDS = new Set([
"statusCheckInterval",
"enableSsh",
"sshPort",
"rdpAuthType",
"protocolAuth",
"jumpHosts",
"createdAt",
"updatedAt",
@@ -368,6 +360,12 @@ export function sanitizeHostForRecipient(
!Array.isArray(authOverrides.ssh)
? (authOverrides.ssh as Record<string, unknown>)
: undefined;
if (stripped.protocolAuth !== undefined) {
stripped.protocolAuth = sanitizeProtocolAuthForRecipient(
stripped.protocolAuth,
permissionLevel,
);
}
if (!sshOverride?.credentialId) {
stripped.hasPassword = false;
stripped.hasKey = false;
@@ -468,10 +466,6 @@ export function transformHostResponse(
shareSshAuth: !!host.shareSshAuth,
enableSsh: !!host.enableSsh,
sshPort: host.sshPort ?? host.port ?? 22,
rdpUser: host.rdpUser || undefined,
rdpDomain: host.rdpDomain || undefined,
vncUser: host.vncUser || undefined,
telnetUser: host.telnetUser || undefined,
jumpHosts: host.jumpHosts ? JSON.parse(host.jumpHosts as string) : [],
// Moved into a plugin's host settings; its hostPayloadLegacy puts it
// back for 2.8 clients.
@@ -490,6 +484,5 @@ export function transformHostResponse(
portKnockSequence: host.portKnockSequence
? JSON.parse(host.portKnockSequence as string)
: [],
domain: host.domain || undefined,
};
}