feat: move terminal settings, look and ssh tools into ssh-terminal plugin

This commit is contained in:
LukeGus committed 2026-09-29 02:38:37 -05:00
1 parent ffa872ebd3
commit d9bba10612
242 files changed
+25071 -7207

No files matched your search

@@ -425,3 +425,57 @@ describe("sanitizeHostForRecipient", () => {
expect(result.password).toBeUndefined();
});
});
describe("transformHostResponse terminal fields", () => {
const row = (extra: Record<string, unknown>) =>
transformHostResponse({ id: 1, tags: "", ...extra });
it("sends the SSH options and core's own terminalConfig keys only", () => {
const host = row({
sshOptions: JSON.stringify({ keepaliveInterval: 20 }),
terminalConfig: JSON.stringify({
startupSnippetId: 3,
theme: "nord",
keepaliveInterval: 5,
}),
});
expect(host.sshOptions).toEqual({ keepaliveInterval: 20 });
expect(host.terminalConfig).toEqual({
keepaliveInterval: 20,
startupSnippetId: 3,
});
});
it("reads the options out of terminal_config before the boot copy", () => {
const host = row({
sshOptions: null,
terminalConfig: JSON.stringify({ agentForwarding: true }),
});
expect(host.sshOptions).toEqual({ agentForwarding: true });
});
it("surfaces a 2.8 sudo password for the sanitizers to strip", () => {
const host = row({
sudoPassword: null,
terminalConfig: JSON.stringify({ sudoPassword: "legacy" }),
});
const stripped = stripSensitiveFields(host);
expect(stripped.hasSudoPassword).toBe(true);
expect(stripped).not.toHaveProperty("sudoPassword");
expect(JSON.stringify(stripped)).not.toContain("legacy");
});
it("hides the owner's agent socket from a shared recipient", () => {
const shared = sanitizeHostForRecipient(
row({
sshOptions: JSON.stringify({
agentSocketPath: "/run/agent",
keepaliveInterval: 9,
}),
}),
"edit",
);
expect(shared.sshOptions).toEqual({ keepaliveInterval: 9 });
expect(shared.terminalConfig).toEqual({ keepaliveInterval: 9 });
});
});
@@ -415,6 +415,22 @@ describe("share levels, legacy fields and generic writes", () => {
expect(host.name).toBe("web");
});
it("adds a plugin's keys to an object core already sends, never over core's", () => {
loaded.push({ id: "docker", manifest: DOCKER, state: "active" });
registryProviders.set("docker.hostPayloadLegacy", () => ({
terminalConfig: { theme: "nord", startupSnippetId: 99 },
}));
const host: Record<string, unknown> = {
id: 2,
terminalConfig: { startupSnippetId: 1 },
};
attachHostPluginSettings(
[host],
new Map([[2, { docker: { enableDocker: true } }]]),
);
expect(host.terminalConfig).toEqual({ theme: "nord", startupSnippetId: 1 });
});
it("imports an export's plugin values, leaving secrets out", async () => {
loaded.push({ id: "remote-desktop", manifest: DESKTOP, state: "active" });
await applyPluginHostImportSettings(9, {
@@ -0,0 +1,97 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const state = vi.hoisted(() => ({
stored: null as Record<string, unknown> | null,
}));
vi.mock("../../../database/repositories/factory.js", () => ({
createCurrentHostResolutionRepository: () => ({
findHostById: async () => state.stored,
}),
}));
const { mergeStoredTerminalFields, parseTerminalConfig } =
await import("../../../database/routes/host-terminal-fields.js");
beforeEach(() => {
state.stored = {
terminalConfig: JSON.stringify({
theme: "nord",
sudoPassword: "legacy-sudo",
agentSocketPath: "/owner/agent",
startupSnippetId: 1,
}),
sshOptions: JSON.stringify({ agentSocketPath: "/owner/agent" }),
};
});
async function merge(
hostData: Record<string, unknown>,
isOwner = true,
sshOptions?: string,
) {
const row: Record<string, unknown> = {
terminalConfig: "replaced wholesale",
...(sshOptions !== undefined ? { sshOptions } : {}),
};
const error = await mergeStoredTerminalFields(row, hostData, 1, "o", isOwner);
return { error, row };
}
describe("parseTerminalConfig", () => {
it("reads objects and JSON, and nothing else", () => {
expect(parseTerminalConfig({ a: 1 })).toEqual({ a: 1 });
expect(parseTerminalConfig('{"a":1}')).toEqual({ a: 1 });
expect(parseTerminalConfig("nope")).toBeNull();
expect(parseTerminalConfig([1])).toBeNull();
});
});
describe("mergeStoredTerminalFields", () => {
it("merges an editor save into the stored 2.8 keys instead of dropping them", async () => {
const { error, row } = await merge({
terminalConfig: { startupSnippetId: 4 },
});
expect(error).toBeNull();
expect(JSON.parse(row.terminalConfig as string)).toEqual({
theme: "nord",
sudoPassword: "legacy-sudo",
agentSocketPath: "/owner/agent",
startupSnippetId: 4,
});
});
it("leaves terminal_config alone when the payload has none", async () => {
const { row } = await merge({});
expect(row).not.toHaveProperty("terminalConfig");
});
it("drops the legacy sudo copy once the owner saves one on its own", async () => {
const { row } = await merge({ sudoPassword: "" });
expect(JSON.parse(row.terminalConfig as string)).not.toHaveProperty(
"sudoPassword",
);
});
it("keeps the owner's private keys through a shared editor's save", async () => {
const { row } = await merge(
{ terminalConfig: { startupSnippetId: 2 } },
false,
JSON.stringify({ keepaliveInterval: 9 }),
);
expect(JSON.parse(row.terminalConfig as string)).toMatchObject({
sudoPassword: "legacy-sudo",
agentSocketPath: "/owner/agent",
startupSnippetId: 2,
});
expect(JSON.parse(row.sshOptions as string)).toEqual({
keepaliveInterval: 9,
agentSocketPath: "/owner/agent",
});
});
it("refuses a terminalConfig it cannot read", async () => {
const { error } = await merge({ terminalConfig: "not json" });
expect(error).toBe("Invalid terminal config");
});
});
@@ -0,0 +1,104 @@
/**
* The admin host defaults. The terminal half moved to the ssh-terminal
* plugin's new-host settings; its keys stay in the stored row for the boot
* copy and a downgrade, but are never served or overwritten here.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import express, { type RequestHandler } from "express";
import request from "supertest";
const state = vi.hoisted(() => ({
settings: {} as Record<string, string>,
admin: true,
}));
vi.mock("../../../utils/logger.js", () => ({
authLogger: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
databaseLogger: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
getGlobalLogLevel: () => "info",
setGlobalLogLevel: vi.fn(),
}));
vi.mock("../../../utils/audit-logger.js", () => ({
logAudit: vi.fn(async () => {}),
getRequestMeta: () => ({ ipAddress: "127.0.0.1", userAgent: "test" }),
}));
vi.mock("../../../utils/analytics.js", () => ({
getTelemetryEnvOverride: () => null,
}));
vi.mock("../../../database/repositories/factory.js", () => ({
createCurrentSettingsRepository: () => ({
get: async (key: string) => state.settings[key] ?? null,
set: async (key: string, value: string) => {
state.settings[key] = value;
},
}),
createCurrentUserRepository: () => ({
findById: async (id: string) => ({
id,
username: "admin",
isAdmin: state.admin,
}),
}),
}));
const { registerUserSettingsRoutes } =
await import("../../../database/routes/user-settings-routes.js");
const authenticate: RequestHandler = (req, _res, next) => {
(req as unknown as { userId: string }).userId = "u1";
next();
};
function app() {
const router = express.Router();
registerUserSettingsRoutes(router, authenticate);
const server = express();
server.use(express.json());
server.use("/users", router);
return server;
}
beforeEach(() => {
state.settings = {};
state.admin = true;
});
describe("/users/host-defaults", () => {
it("serves core's defaults without the terminal keys the plugin took over", async () => {
state.settings.host_defaults = JSON.stringify({
useSocks5: true,
fontSize: 18,
autoTmux: true,
});
const response = await request(app()).get("/users/host-defaults");
expect(response.status).toBe(200);
expect(response.body).toEqual({ useSocks5: true });
});
it("keeps the stored terminal keys when an admin saves core's defaults", async () => {
state.settings.host_defaults = JSON.stringify({
useSocks5: true,
fontSize: 18,
enableCommandHistory: false,
});
const response = await request(app())
.patch("/users/host-defaults")
.send({ useSocks5: false, statusCheckEnabled: false, autoTmux: true });
expect(response.status).toBe(200);
expect(JSON.parse(state.settings.host_defaults)).toEqual({
fontSize: 18,
enableCommandHistory: false,
useSocks5: false,
statusCheckEnabled: false,
});
});
it("refuses a user who is not an admin", async () => {
state.admin = false;
const response = await request(app())
.patch("/users/host-defaults")
.send({ useSocks5: true });
expect(response.status).toBe(403);
});
});