Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { AuditRecord } from "../schema/schema.js";
import { canonicalBytes, signRecord, verifyRecord } from "./hmac.js";
const KEY = new TextEncoder().encode("test-key-32-bytes-padding-padding!");
const RECORD: AuditRecord = {
ts: "2026-05-26T10:00:00.000Z",
type: "admin.plugin.disabled",
principal: { id: "user_1", scopes: ["admin"] },
tenant: "tenant_a",
request_id: "req_1",
trace_id: null,
subject: "plugin.exports",
outcome: "success",
};
const FAILURE: AuditRecord = {
...RECORD,
outcome: "failure",
cause: { code: "PLUGIN.NOT_FOUND", message: "no such plugin" },
};
describe("canonicalBytes", () => {
it("is deterministic across calls", { timeout: 5000 }, () => {
const a = canonicalBytes(RECORD);
const b = canonicalBytes(RECORD);
expect(a).toEqual(b);
});
it("omits cause when absent", { timeout: 5000 }, () => {
const json = new TextDecoder().decode(canonicalBytes(RECORD));
expect(json.includes('"cause"')).toBe(false);
});
it("includes cause when present", { timeout: 5000 }, () => {
const json = new TextDecoder().decode(canonicalBytes(FAILURE));
expect(json.includes('"cause"')).toBe(true);
});
it("emits keys in design-doc §12.5 order", { timeout: 5000 }, () => {
const json = new TextDecoder().decode(canonicalBytes(RECORD));
// Top-level keys appear in the fixed order
const order = [
'"ts"',
'"type"',
'"principal"',
'"tenant"',
'"request_id"',
'"trace_id"',
'"subject"',
'"outcome"',
];
let last = -1;
for (const k of order) {
const idx = json.indexOf(k);
expect(idx).toBeGreaterThan(last);
last = idx;
}
});
});
describe("signRecord / verifyRecord", () => {
it("HMAC verifies under the same key", { timeout: 5000 }, () => {
const tag = signRecord(RECORD, KEY);
expect(verifyRecord(RECORD, KEY, tag)).toBe(true);
});
it("HMAC is hex-encoded and length-64 for SHA-256", {
timeout: 5000,
}, () => {
const tag = signRecord(RECORD, KEY);
expect(tag.length).toBe(64);
expect(/^[0-9a-f]+$/.test(tag)).toBe(true);
});
it("tampering invalidates HMAC", { timeout: 5000 }, () => {
const tag = signRecord(RECORD, KEY);
const tampered: AuditRecord = {
...RECORD,
outcome: "failure",
cause: { code: "X", message: "y" },
};
expect(verifyRecord(tampered, KEY, tag)).toBe(false);
});
it("wrong key fails verification", { timeout: 5000 }, () => {
const tag = signRecord(RECORD, KEY);
const other = new TextEncoder().encode(
"different-key-padding-padding!!!"
);
expect(verifyRecord(RECORD, other, tag)).toBe(false);
});
it("malformed hex returns false", { timeout: 5000 }, () => {
expect(verifyRecord(RECORD, KEY, "zz")).toBe(false);
expect(verifyRecord(RECORD, KEY, "abc")).toBe(false);
});
it("length mismatch returns false", { timeout: 5000 }, () => {
expect(verifyRecord(RECORD, KEY, "ab")).toBe(false);
});
});