* fix: only show connection lost when the server check fails too

* fix: load the macOS tray icon from the right unpacked folder (#1728)

* fix: hide update and review buttons on disabled plugins (#1745)

* fix: save the database without blocking the server, bump to 26.11.0

* fix: try the key before the password for hosts with both (Plugin-SSH-Terminal#50)

* fix: add macOS Window menu item to reopen main window

* feat: upload Mac App Store screenshots on submit

* chore: drop internal note from changelog

* chore: update translations and plugin pins

* chore: lint, format, and bump version to 26.11.0
This commit is contained in:
Luke Gustafson authored and GitHub committed 2026-11-01 17:16:04 -06:00
1 parent c759a147c6
commit 1d4eac3e20
24 files changed
+337 -56

No files matched your search

@@ -0,0 +1,68 @@
import { afterEach, describe, expect, it } from "vitest";
import ssh2 from "ssh2";
const { Client, Server, utils } = ssh2;
// The order prepareKey sets when a host has both a key and a password.
const AUTH_ORDER = ["publickey", "password", "keyboard-interactive"];
const keys = utils.generateKeyPairSync("ed25519");
let server: InstanceType<typeof Server> | null = null;
afterEach(() => server?.close());
/** A server with AuthenticationMethods publickey,password. */
function start(): Promise<number> {
server = new Server({ hostKeys: [keys.private] }, (client) => {
let keyDone = false;
client.on("authentication", (ctx) => {
if (ctx.method === "publickey") {
if (ctx.signature) {
keyDone = true;
ctx.reject(["password"], true);
} else {
ctx.accept();
}
} else if (ctx.method === "password" && keyDone) {
ctx.accept();
} else {
ctx.reject(keyDone ? ["password"] : ["publickey"]);
}
});
client.on("ready", () => client.end());
client.on("error", () => {});
});
return new Promise((resolve) => {
server!.listen(0, "127.0.0.1", () =>
resolve((server!.address() as { port: number }).port),
);
});
}
function connect(port: number, authHandler?: string[]) {
return new Promise<string>((resolve) => {
const client = new Client();
client.on("ready", () => {
client.end();
resolve("ready");
});
client.on("error", (error) => resolve(error.message));
client.connect({
host: "127.0.0.1",
port,
username: "u",
privateKey: keys.private,
password: "pw",
...(authHandler ? { authHandler } : {}),
} as never);
});
}
describe("a server that wants a key, then a password", () => {
it("logs in with the key first order", async () => {
expect(await connect(await start(), AUTH_ORDER)).toBe("ready");
});
it("fails with ssh2's default order, which tries the password first", async () => {
expect(await connect(await start())).toMatch(/authentication/i);
});
});
@@ -205,6 +205,11 @@ describe("buildConnectConfig per auth type", () => {
expect(Buffer.isBuffer(config.privateKey)).toBe(true);
expect(config.passphrase).toBe("secret");
expect(config.password).toBe("fallback");
expect(config.authHandler).toEqual([
"publickey",
"password",
"keyboard-interactive",
]);
expect(mocks.applyCertificateAuth).not.toHaveBeenCalled();
});