mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-11-01 08:05:23 +00:00
v26.10.3 (#1692)
* feat: shell controller, TabStage, layout and theme plugins with repair view * feat: move the classic layout into its own bundled plugin * feat: move color themes into a bundled themes plugin * fix: plugin tests and type checks work with a linked sdk * fix: home tab, layout switch and missing layout fallbacks for looks * chore: add classic layout and themes to the readme * chore: drop example plugin links from the plugin guide * feat: plugin filters by type, paged browse, hide plugins needing newer termix * feat: move desktop, status, import, audit and branding features into plugins * feat: host lock api so a plugin can load hosts from a config file (#1581) * chore: add hosts config to the plugin table * feat: login policy hook for plugins and optional https redirect (#1584) * chore: add login security to the plugin table * feat: api key socket tickets so a master can open remote sessions (#1654) * feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654) * chore: add sync to the plugin table * feat: move the sync screens and wizard out of core into the plugin (#1580) * fix: add a neutral linked desktop hook for plugins (#1580) * fix: show the linked account's name and admin flag in the app (#1580) * feat: ssh transport providers, core ssh relay and plugin folders api * feat: support FIDO2 security keys through ssh agent * feat: add companion panels beside host tabs * feat: mTLS client certificates for desktop server links * feat: native web view for plugins in the desktop app * fix: detect the desktop link window by Electron's default user agent Fixes #1675 * fix: changelog for desktop link fix * fix: keep text primary keys in the plugin data export * fix: ship sharp binaries for Windows arm64 builds * feat: client cert on the sync link types (sdk 1.0.18) * chore: update translations * feat: themed title bar for the desktop app (#1690) * chore: changelog for the themed title bar * fix: draw the title bar in the sidebar color * feat: per host layout for the terminal panel bar * chore: sdk 1.0.19 for the panel bar layout * chore: bundle all 43 plugins for 26.10.3 * chore: update translations --------- Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com> Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
This commit is contained in:
577 files changed
+76479
-101831
No files matched your search
@@ -0,0 +1,147 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
hosts: [] as Record<string, unknown>[],
|
||||
credentials: [] as Record<string, unknown>[],
|
||||
resolve: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../database/repositories/factory.js", () => ({
|
||||
createCurrentHostResolutionRepository: () => ({
|
||||
findHostsByUserId: async () => state.hosts,
|
||||
findHostByIdForUser: async (id: number) =>
|
||||
state.hosts.find((host) => host.id === id) ?? null,
|
||||
}),
|
||||
createCurrentCredentialRepository: () => ({
|
||||
listDecryptedByUserId: async () => state.credentials,
|
||||
}),
|
||||
}));
|
||||
vi.mock("../../../database/routes/host-normalizers.js", () => ({
|
||||
hostTerminalExport: () => ({ terminalConfig: null, sshOptions: null }),
|
||||
}));
|
||||
vi.mock("../../../database/routes/host-plugin-settings.js", () => ({
|
||||
loadHostPluginSettings: async () => new Map(),
|
||||
}));
|
||||
vi.mock("../../../hosts/protocol-auth/protocol-auth.js", () => ({
|
||||
listProtocolLogins: async () => [],
|
||||
toPortableLogins: () => ({
|
||||
rdp: {
|
||||
authType: "credential",
|
||||
credentialId: 4,
|
||||
username: "bob",
|
||||
password: "rdp-secret",
|
||||
fields: { domain: "corp", token: "t" },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
vi.mock("../../../hosts/protocol-auth/registry.js", () => ({
|
||||
findHostProtocol: () => ({
|
||||
credentialFields: [{ key: "token", secret: true }, { key: "domain" }],
|
||||
}),
|
||||
}));
|
||||
vi.mock("../../../hosts/resolve-host-credentials.js", () => ({
|
||||
resolveHostCredentials: state.resolve,
|
||||
}));
|
||||
|
||||
import { exportAllHosts } from "../../../hosts/import/host-export.js";
|
||||
|
||||
const sshHost = {
|
||||
id: 1,
|
||||
connectionType: "ssh",
|
||||
name: "web",
|
||||
ip: "192.0.2.1",
|
||||
port: 22,
|
||||
username: "alice",
|
||||
password: "pw",
|
||||
key: "KEY",
|
||||
keyPassword: "kp",
|
||||
sudoPassword: "sp",
|
||||
socks5Password: "s5",
|
||||
authType: "credential",
|
||||
credentialId: 7,
|
||||
folder: "Prod",
|
||||
tags: "a,b",
|
||||
jumpHosts: JSON.stringify([{ hostId: 2 }]),
|
||||
forceKeyboardInteractive: "true",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
state.hosts = [{ ...sshHost }];
|
||||
state.credentials = [
|
||||
{ id: 7, name: "deploy", authType: "key", username: "alice", tags: "x" },
|
||||
];
|
||||
state.resolve.mockReset().mockImplementation(async (host) => ({
|
||||
...host,
|
||||
password: "resolved-pw",
|
||||
key: "RESOLVED-KEY",
|
||||
}));
|
||||
});
|
||||
|
||||
describe("exportAllHosts", () => {
|
||||
it("includes decrypted secrets without share mode", async () => {
|
||||
const result = await exportAllHosts("u1", false);
|
||||
expect(result.credentials).toBeUndefined();
|
||||
expect(result.hosts[0]).toMatchObject({
|
||||
exportId: 1,
|
||||
password: "resolved-pw",
|
||||
key: "RESOLVED-KEY",
|
||||
sudoPassword: "sp",
|
||||
credentialId: 7,
|
||||
tags: ["a", "b"],
|
||||
jumpHosts: [{ hostId: 2 }],
|
||||
forceKeyboardInteractive: true,
|
||||
});
|
||||
expect(state.resolve).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("leaves out every secret and credential id in share mode", async () => {
|
||||
const result = await exportAllHosts("u1", true);
|
||||
const [host] = result.hosts;
|
||||
expect(state.resolve).not.toHaveBeenCalled();
|
||||
expect(host).toMatchObject({
|
||||
password: null,
|
||||
key: null,
|
||||
keyPassword: null,
|
||||
sudoPassword: null,
|
||||
socks5Password: null,
|
||||
credentialAlias: "deploy",
|
||||
});
|
||||
expect(host).not.toHaveProperty("credentialId");
|
||||
expect(result.credentials).toEqual([
|
||||
expect.objectContaining({ alias: "deploy", authType: "key" }),
|
||||
]);
|
||||
expect(JSON.stringify(result)).not.toMatch(/rdp-secret|"t"/);
|
||||
expect(host.protocolAuth).toMatchObject({
|
||||
rdp: {
|
||||
authType: "direct",
|
||||
credentialId: null,
|
||||
password: null,
|
||||
fields: { domain: "corp" },
|
||||
},
|
||||
});
|
||||
expect(result.version).toBe("1");
|
||||
});
|
||||
|
||||
it("exports a remote desktop host without SSH fields", async () => {
|
||||
state.hosts = [{ ...sshHost, connectionType: "rdp" }];
|
||||
const result = await exportAllHosts("u1", false);
|
||||
expect(result.hosts[0]).not.toHaveProperty("authType");
|
||||
expect(result.hosts[0]).toHaveProperty("protocolAuth");
|
||||
});
|
||||
});
|
||||
|
||||
describe("exportAllHosts with hostIds", () => {
|
||||
it("exports only the asked hosts the user owns", async () => {
|
||||
state.hosts = [
|
||||
{ ...sshHost, id: 1 },
|
||||
{ ...sshHost, id: 2 },
|
||||
];
|
||||
const result = await exportAllHosts("u1", false, [2, 99]);
|
||||
expect(result.hosts.map((host) => host.exportId)).toEqual([2]);
|
||||
});
|
||||
|
||||
it("returns nothing for an id the user does not own", async () => {
|
||||
const result = await exportAllHosts("u1", false, [99]);
|
||||
expect(result.hosts).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user