AuditActor gains ignoreRequestUser: buildAuditHeaders then omits the forwarded PB token (X-Audit-Auth) and request-derived email hint, so the PB audit hook falls back to X-Audit-Actor-Name. fillQuote sets it for all its writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.4 KiB
TypeScript
90 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { buildAuditHeaders, runWithAuditActor } from "./audit-context";
|
|
|
|
const reader = (h: Record<string, string>) => ({
|
|
get: (name: string) => h[name.toLowerCase()] ?? null,
|
|
});
|
|
|
|
describe("buildAuditHeaders", () => {
|
|
it("forwards the dashboard user's PB token from Authorization", () => {
|
|
const out = buildAuditHeaders(reader({ authorization: "pb-user-token" }), undefined, "agent-key");
|
|
expect(out).toMatchObject({ "X-Audit-Source": "web", "X-Audit-Auth": "pb-user-token" });
|
|
});
|
|
|
|
it("uses the fetch-wrapper header when Authorization is absent", () => {
|
|
const out = buildAuditHeaders(reader({ "x-wfdash-auth": "tok" }), undefined, "agent-key");
|
|
expect(out["X-Audit-Auth"]).toBe("tok");
|
|
});
|
|
|
|
it("never forwards the agent API key as a user token", () => {
|
|
const out = buildAuditHeaders(reader({ authorization: "Bearer agent-key" }), undefined, "agent-key");
|
|
expect(out["X-Audit-Source"]).toBe("agent");
|
|
expect(out["X-Audit-Auth"]).toBeUndefined();
|
|
});
|
|
|
|
it("prefers the wrapper token over the agent key for agent-authed browser calls", () => {
|
|
const out = buildAuditHeaders(
|
|
reader({ authorization: "Bearer agent-key", "x-wfdash-auth": "tok" }),
|
|
undefined,
|
|
"agent-key",
|
|
);
|
|
expect(out).toMatchObject({ "X-Audit-Source": "agent", "X-Audit-Auth": "tok" });
|
|
});
|
|
|
|
it("carries an explicit actor, URI-encoding non-ASCII names", () => {
|
|
const out = buildAuditHeaders(null, { id: "u1", name: "Zoë Naidoo", source: "mcp" }, undefined);
|
|
expect(out).toMatchObject({
|
|
"X-Audit-Source": "mcp",
|
|
"X-Audit-Actor-Id": "u1",
|
|
"X-Audit-Actor-Name": "Zo%C3%AB%20Naidoo",
|
|
});
|
|
});
|
|
|
|
it("reads the dev-identity cookie as an email hint but never invents one", () => {
|
|
expect(
|
|
buildAuditHeaders(reader({ cookie: "a=1; wfdash_dev_email=sam%40wf.co.za" }), undefined, undefined)[
|
|
"X-Audit-Actor-Email"
|
|
],
|
|
).toBe("sam%40wf.co.za");
|
|
expect(buildAuditHeaders(reader({}), undefined, undefined)["X-Audit-Actor-Email"]).toBeUndefined();
|
|
});
|
|
|
|
it("omits the request user's token and email hint when the actor opts out (ignoreRequestUser)", () => {
|
|
const incoming = reader({
|
|
authorization: "Bearer pb-user-token",
|
|
"x-wfdash-auth": "tok",
|
|
cookie: "wfdash_dev_email=sam%40wf.co.za",
|
|
});
|
|
const out = buildAuditHeaders(
|
|
incoming,
|
|
{ name: "AI quote assistant (run by Sam)", source: "agent", ignoreRequestUser: true },
|
|
"agent-key",
|
|
);
|
|
expect(out["X-Audit-Auth"]).toBeUndefined();
|
|
expect(out["X-Audit-Actor-Email"]).toBeUndefined();
|
|
expect(out).toMatchObject({
|
|
"X-Audit-Source": "agent",
|
|
"X-Audit-Actor-Name": encodeURIComponent("AI quote assistant (run by Sam)"),
|
|
});
|
|
});
|
|
|
|
it("still forwards the request user's token when the actor does not opt out", () => {
|
|
const out = buildAuditHeaders(
|
|
reader({ authorization: "Bearer pb-user-token" }),
|
|
{ name: "Someone", source: "mcp" },
|
|
"agent-key",
|
|
);
|
|
expect(out["X-Audit-Auth"]).toBe("pb-user-token");
|
|
});
|
|
|
|
it("falls back to source=server outside a request", () => {
|
|
expect(buildAuditHeaders(null, undefined, undefined)).toEqual({ "X-Audit-Source": "server" });
|
|
});
|
|
});
|
|
|
|
describe("runWithAuditActor", () => {
|
|
it("returns the callback's value", async () => {
|
|
await expect(runWithAuditActor({ source: "mcp" }, async () => 42)).resolves.toBe(42);
|
|
});
|
|
});
|