mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
fix: count plugin ssh logins toward host status and stop showing reachable as a warning
This commit is contained in:
1 parent
69b1b9805b
commit
f97f35de1f
12 files changed
+143
-25
No files matched your search
@@ -9,7 +9,7 @@ vi.mock("../../../utils/logger.js", () => ({
|
||||
pluginLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const { HostStatusService, toStatusTarget } =
|
||||
const { HostStatusService, toStatusTarget, LOGIN_FRESH_MS } =
|
||||
await import("../../../hosts/status/host-status-service.js");
|
||||
const { hostSessionStatus } =
|
||||
await import("../../../hosts/host-session-status.js");
|
||||
@@ -35,6 +35,7 @@ function setup(
|
||||
targets: Target[],
|
||||
reachable = true,
|
||||
shared: Record<string, number[]> = {},
|
||||
clock = { now: 1_000_000 },
|
||||
) {
|
||||
const emitted: unknown[] = [];
|
||||
const ping = vi.fn(async () => reachable);
|
||||
@@ -54,6 +55,7 @@ function setup(
|
||||
pingThroughJumpHosts,
|
||||
globalInterval: () => 60,
|
||||
emit: (payload) => emitted.push(payload),
|
||||
now: () => clock.now,
|
||||
});
|
||||
return { service, emitted, ping, pingThroughJumpHosts, loadTargets };
|
||||
}
|
||||
@@ -206,9 +208,53 @@ describe("HostStatusService", () => {
|
||||
const release = hostSessionStatus.register(1);
|
||||
expect(service.get(1)?.status).toBe("online");
|
||||
release();
|
||||
expect(service.get(1)?.status).toBe("online");
|
||||
});
|
||||
|
||||
it("keeps a host online for a while after its last login", async () => {
|
||||
const clock = { now: 1_000_000 };
|
||||
const { service } = setup([target(1)], true, {}, clock);
|
||||
active = service;
|
||||
await service.statusesFor("owner", null);
|
||||
await flush();
|
||||
|
||||
service.reportLogin(1, { ok: true });
|
||||
clock.now += LOGIN_FRESH_MS - 1;
|
||||
vi.advanceTimersByTime(75_000);
|
||||
await flush();
|
||||
expect(service.get(1)?.status).toBe("online");
|
||||
|
||||
clock.now += 2;
|
||||
vi.advanceTimersByTime(75_000);
|
||||
await flush();
|
||||
expect(service.get(1)?.status).toBe("reachable");
|
||||
});
|
||||
|
||||
it("drops an ended session back to reachable once the login is stale", async () => {
|
||||
const clock = { now: 1_000_000 };
|
||||
const { service } = setup([target(1)], true, {}, clock);
|
||||
active = service;
|
||||
service.start();
|
||||
await service.statusesFor("owner", null);
|
||||
await flush();
|
||||
|
||||
const release = hostSessionStatus.register(1);
|
||||
clock.now += LOGIN_FRESH_MS;
|
||||
release();
|
||||
expect(service.get(1)?.status).toBe("reachable");
|
||||
});
|
||||
|
||||
it("shows an offline host as offline even after a login", async () => {
|
||||
const { service } = setup([target(1)], false);
|
||||
active = service;
|
||||
await service.statusesFor("owner", null);
|
||||
await flush();
|
||||
service.reportLogin(1, { ok: true });
|
||||
vi.advanceTimersByTime(75_000);
|
||||
await flush();
|
||||
expect(service.get(1)?.status).toBe("offline");
|
||||
});
|
||||
|
||||
it("only checks the requested hosts on the desktop app", async () => {
|
||||
const { service, ping } = setup([target(1), target(2)]);
|
||||
active = service;
|
||||
|
||||
@@ -22,11 +22,12 @@ describe("isHostKeyVerificationError", () => {
|
||||
|
||||
describe("host availability status", () => {
|
||||
it("does not call a TCP-reachable host online before authentication", () => {
|
||||
expect(statusAfterReachabilityCheck(true)).toBe("reachable");
|
||||
expect(statusAfterReachabilityCheck(true, false)).toBe("reachable");
|
||||
});
|
||||
|
||||
it("keeps a verified host online across later reachability checks", () => {
|
||||
expect(statusAfterReachabilityCheck(true, "online")).toBe("online");
|
||||
it("keeps a recently verified host online across reachability checks", () => {
|
||||
expect(statusAfterReachabilityCheck(true, true)).toBe("online");
|
||||
expect(statusAfterReachabilityCheck(false, true)).toBe("offline");
|
||||
});
|
||||
|
||||
it("marks successful authentication online", () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ const h = vi.hoisted(() => ({
|
||||
revoked: [] as Array<{ userId: string; except?: string }>,
|
||||
trustedCleared: [] as string[],
|
||||
policyError: null as Error | null,
|
||||
logins: [] as Array<{ hostId: number; outcome: unknown }>,
|
||||
}));
|
||||
|
||||
vi.mock("../../utils/logger.js", () => {
|
||||
@@ -88,6 +89,12 @@ vi.mock("../../hosts/connect/connect-host.js", () => ({
|
||||
return { client, jumpClient: null, dispose: vi.fn(() => client.end()) };
|
||||
},
|
||||
}));
|
||||
vi.mock("../../hosts/status/host-status-service.js", () => ({
|
||||
hostStatusService: {
|
||||
reportLogin: (hostId: number, outcome: unknown) =>
|
||||
h.logins.push({ hostId, outcome }),
|
||||
},
|
||||
}));
|
||||
vi.mock("../../database/repositories/factory.js", () => ({
|
||||
createCurrentUserAuthRepository: () => ({
|
||||
recordSecondFactor: async (
|
||||
@@ -141,6 +148,7 @@ beforeEach(() => {
|
||||
h.cleared = [];
|
||||
h.clearedAll = 0;
|
||||
h.pooled = [];
|
||||
h.logins = [];
|
||||
});
|
||||
|
||||
describe("ctx.ssh", () => {
|
||||
@@ -184,6 +192,19 @@ describe("ctx.ssh", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a login to a saved host's status, never to a plugin's own host", async () => {
|
||||
h.granted = new Set(["ssh:connect", "credentials:use"]);
|
||||
const ssh = createPluginSsh({
|
||||
manifest: manifest(["ssh:connect", "credentials:use"]),
|
||||
bag: new DisposableBag("fixture"),
|
||||
audit: vi.fn(async () => {}),
|
||||
});
|
||||
await ssh.connect(7);
|
||||
await ssh.connect({ id: 9, ip: "h", port: 22, username: "u" } as never);
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
expect(h.logins).toEqual([{ hostId: 7, outcome: { ok: true } }]);
|
||||
});
|
||||
|
||||
it("never believes a host object's own userId", async () => {
|
||||
h.actor = undefined;
|
||||
h.granted = new Set(["ssh:connect", "credentials:use"]);
|
||||
|
||||
Reference in new issue
Block a user