fix(hosts): display protocol credential usernames for non-SSH hosts (#1572)

This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-10-07 16:37:58 -05:00
1 parent bb4b345feb
commit 42c5682243
4 files changed
+88 -4

No files matched your search

@@ -434,12 +434,25 @@ export async function loadProtocolAuthSummaries(
const rows =
await createCurrentHostProtocolAuthRepository().listRowsForHosts(ids);
const keys = new Map<string, Buffer | null>();
const usernames = new Map<string, string | null>();
for (const row of rows) {
if (!keys.has(row.userId)) {
keys.set(row.userId, DataCrypto.getUserDataKey(row.userId));
}
const own = result.get(row.hostId) ?? {};
own[row.protocol] = summarize(row, keys.get(row.userId) ?? null);
const summary = summarize(row, keys.get(row.userId) ?? null);
if (row.authType === "credential" && row.credentialId) {
const key = `${row.userId}:${row.credentialId}`;
if (!usernames.has(key)) {
const credential = await findUsableCredential(
row.credentialId,
row.userId,
);
usernames.set(key, credential?.username || null);
}
summary.username = usernames.get(key) ?? null;
}
own[row.protocol] = summary;
result.set(row.hostId, own);
}
return result;
@@ -329,6 +329,23 @@ describe("a plugin protocol core does not know", () => {
});
});
it("summarizes the usable credential username without exposing its secret", async () => {
const patch = readProtocolAuthPayload({
protocolAuth: { spice: { authType: "credential", credentialId: 6 } },
});
await writeProtocolAuth("owner", HOST_ID, patch!, { isOwner: true });
const summaries = await loadProtocolAuthSummaries([{ id: HOST_ID }]);
expect(summaries.get(HOST_ID)?.spice.username).toBe("cred-user");
expect(JSON.stringify(summaries.get(HOST_ID))).not.toContain("cred-pass");
const recipient = sanitizeHostForRecipient(
{ protocolAuth: summaries.get(HOST_ID) },
"connect",
);
expect(recipient.protocolAuth).toEqual({
spice: { authType: "credential" },
});
});
it("lets a shared editor change the login but not its credential", async () => {
await storeLogin();
const rename = readProtocolAuthPayload({
+17 -3
View File
@@ -148,10 +148,19 @@ const DOUBLE_CLICK_WINDOW_MS = 250;
const DEFAULT_ROW_FIELDS = defaultHostRowFields();
export function formatHostAddress(
host: Pick<Host, "username" | "ip" | "port">,
host: Pick<Host, "username" | "ip" | "port"> &
Partial<Pick<Host, "enableSsh" | "pluginSettings" | "protocolAuth">>,
fields: Pick<HostRowFields, "showUsername" | "showPort">,
): string {
const user = fields.showUsername && host.username ? `${host.username}@` : "";
const protocol =
host.enableSsh === false ? enabledHostProtocols(host)[0] : undefined;
const username =
host.enableSsh === false
? protocol
? host.protocolAuth?.[protocol.id]?.username
: undefined
: host.username;
const user = fields.showUsername && username ? `${username}@` : "";
const port = fields.showPort && host.port ? `:${host.port}` : "";
return `${user}${host.ip}${port}`;
}
@@ -787,7 +796,12 @@ export function HostItem({
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
writeClipboardText(`${host.username}@${host.ip}`);
writeClipboardText(
formatHostAddress(host, {
showUsername: true,
showPort: false,
}),
);
toast.success(t("hosts.copiedToClipboard"));
}}
>
@@ -1,3 +1,4 @@
import { registerHostProtocol } from "@/sidebar/host-protocols";
import {
act,
cleanup,
@@ -402,3 +403,42 @@ describe("HostItem row fields", () => {
).toBe("root@10.0.0.5");
});
});
it("uses the enabled protocol username for non-SSH hosts", () => {
const dispose = registerHostProtocol({
id: "rdp",
pluginId: "remote-desktop",
settingKey: "enableRdp",
defaultPort: 3389,
titleKey: "rdp",
icon: () => null,
});
try {
const host: Host = {
...baseHost,
enableSsh: false,
pluginSettings: { "remote-desktop": { enableRdp: true } },
protocolAuth: {
rdp: {
authType: "credential",
credentialId: 6,
username: "Administrator",
fields: {},
},
},
};
const fields = { showUsername: true, showPort: false };
expect(formatHostAddress(host, fields)).toBe("Administrator@10.0.0.5");
expect(formatHostAddress({ ...host, enableSsh: true }, fields)).toBe(
"root@10.0.0.5",
);
expect(formatHostAddress({ ...host, protocolAuth: {} }, fields)).toBe(
"10.0.0.5",
);
expect(formatHostAddress(host, { ...fields, showUsername: false })).toBe(
"10.0.0.5",
);
} finally {
dispose();
}
});