feat(diagnostics): correlate staff operations errors and audit records
This commit is contained in:
1 parent
440ce4e556
commit
a2954e4408
14 files changed
+473
-110
No files matched your search
@@ -49,6 +49,12 @@ vi.mock("@/lib/db", () => ({
|
||||
|
||||
vi.mock("@/env", () => ({ env: {} }));
|
||||
|
||||
import {
|
||||
createStore,
|
||||
runWithStore,
|
||||
setContextUserId,
|
||||
} from "@/lib/foundation/request-context";
|
||||
import type { IpAddress, UserId } from "@/lib/foundation/types";
|
||||
import { getAuditLogs, logAudit } from "./audit";
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -56,6 +62,25 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("logAudit", () => {
|
||||
it("stores the same trusted operation ID across asynchronous audit writes", async () => {
|
||||
const store = createStore("test" as IpAddress);
|
||||
await runWithStore(store, async () => {
|
||||
setContextUserId(42 as UserId);
|
||||
await Promise.resolve();
|
||||
await logAudit({
|
||||
userId: 42,
|
||||
action: "update",
|
||||
target: "user",
|
||||
after: { name: "Alice" },
|
||||
});
|
||||
});
|
||||
expect(JSON.parse(insertValues.mock.calls[0][0].details)).toEqual({
|
||||
operationId: store.requestId,
|
||||
userId: 42,
|
||||
});
|
||||
await logAudit({ userId: 42, action: "outside", target: "user" });
|
||||
expect(JSON.parse(insertValues.mock.calls[1][0].details)).toEqual({});
|
||||
});
|
||||
it("creates an audit entry", async () => {
|
||||
insertValues.mockResolvedValue({ id: 1 });
|
||||
await logAudit({
|
||||
@@ -189,6 +214,40 @@ describe("getAuditLogs", () => {
|
||||
});
|
||||
|
||||
describe("audit response privacy", () => {
|
||||
it("exposes only a validated operation ID, never arbitrary stored details", async () => {
|
||||
selectRows.mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
userId: 42,
|
||||
diff: null,
|
||||
before: null,
|
||||
after: null,
|
||||
operationDetails: JSON.stringify({
|
||||
operationId: "op-123",
|
||||
token: "private-token",
|
||||
ip: "private-ip",
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userId: 42,
|
||||
diff: null,
|
||||
before: null,
|
||||
after: null,
|
||||
operationDetails: JSON.stringify({
|
||||
operationId: "../../other?token=private",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
selectCount.mockResolvedValue([{ value: 2 }]);
|
||||
selectUsers.mockResolvedValue([]);
|
||||
const result = await getAuditLogs();
|
||||
expect(result.rows[0].operationId).toBe("op-123");
|
||||
expect(result.rows[1].operationId).toBeNull();
|
||||
expect(JSON.stringify(result.rows)).not.toContain("private-token");
|
||||
expect(JSON.stringify(result.rows)).not.toContain("private-ip");
|
||||
expect(result.rows[0]).not.toHaveProperty("operationDetails");
|
||||
});
|
||||
it("does not serialize legacy snapshot secrets to the client", async () => {
|
||||
selectRows.mockResolvedValue([
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user