feat(rcon): add audited command history
CI / validate (push) Successful in 6m38s
Release / release (push) Successful in 8m29s

This commit is contained in:
dmg
2026-08-13 21:26:24 -04:00
parent 168a7a2c36
commit ed3f3cd843
12 changed files with 338 additions and 21 deletions
@@ -6,7 +6,8 @@ const actionState = vi.hoisted(() => ({
transactionSelected: [] as unknown[],
updates: [] as Record<string, unknown>[],
inserts: [] as unknown[],
audits: [] as Array<{ admin: unknown; subject: string; type: string; data: Record<string, unknown> }>,
audits: [] as Array<{ admin: unknown; subject: string; type: string; data: Record<string, unknown>; correlationId?: string }>,
auditFailure: false,
executions: [] as Array<{ connection: Record<string, unknown>; command: string }>,
gatewayResult: { ok: true, response: "private response" } as
| { ok: true; response: string }
@@ -84,8 +85,11 @@ vi.mock("@/lib/audit", () => ({
subject: string,
type: string,
data: Record<string, unknown>,
options?: { correlationId?: string },
) => {
actionState.audits.push({ admin, subject, type, data });
if (actionState.auditFailure) throw new Error("audit unavailable");
actionState.audits.push({ admin, subject, type, data, correlationId: options?.correlationId });
return "22222222-2222-4222-8222-222222222222";
},
}));
@@ -118,6 +122,7 @@ describe("RCON server actions", () => {
actionState.updates = [];
actionState.inserts = [];
actionState.audits = [];
actionState.auditFailure = false;
actionState.executions = [];
actionState.gatewayResult = { ok: true, response: "private response" };
});
@@ -154,7 +159,7 @@ describe("RCON server actions", () => {
expect(JSON.stringify(actionState.inserts)).not.toContain("decrypted-password");
});
it("rechecks enabled saved state and records credential-safe command lifecycle audits", async () => {
it("rechecks enabled saved state and records complete command lifecycle audits", async () => {
actionState.selected = [savedServer];
const formData = new FormData();
formData.set("serverId", serverId);
@@ -176,16 +181,19 @@ describe("RCON server actions", () => {
admin: { email: "admin@example.test", name: "Admin" },
subject: `rcon-server/${serverId}`,
type: "games.minecraft.account-manager.rcon.command.requested",
data: expect.objectContaining({ verb: "say", commandDigest: "hmac-sha256:v1:digest" }),
data: expect.objectContaining({ command: "say private value", verb: "say", commandDigest: "hmac-sha256:v1:digest" }),
correlationId: expect.stringMatching(/^[0-9a-f-]{36}$/),
}),
expect.objectContaining({
subject: `rcon-server/${serverId}`,
type: "games.minecraft.account-manager.rcon.command.completed",
data: expect.objectContaining({ success: true, durationMs: expect.any(Number) }),
correlationId: expect.stringMatching(/^[0-9a-f-]{36}$/),
}),
]);
const serializedAudits = JSON.stringify(actionState.audits);
expect(serializedAudits).not.toContain("private value");
expect(actionState.audits[0]?.correlationId).toBe(actionState.audits[1]?.correlationId);
expect(serializedAudits).toContain("private value");
expect(serializedAudits).not.toContain("private response");
expect(serializedAudits).not.toContain("decrypted-password");
});
@@ -209,6 +217,21 @@ describe("RCON server actions", () => {
expect(JSON.stringify(actionState.audits)).not.toContain("decrypted-password");
});
it("does not send a command when its requested audit cannot be recorded", async () => {
actionState.selected = [savedServer];
actionState.auditFailure = true;
const formData = new FormData();
formData.set("serverId", serverId);
formData.set("command", "list");
await expect(executeRconCommand({ status: "idle", message: "", serverId: "" }, formData)).resolves.toEqual({
status: "error",
message: "Command not sent because its audit record could not be created.",
serverId,
});
expect(actionState.executions).toEqual([]);
});
it("does not execute or audit when the enabled connection is unavailable", async () => {
const formData = new FormData();
formData.set("serverId", serverId);