libs/juava/__tests__/functions-auth.test.ts
Rama newjitsu · réplica de lectura del repositorio original.
import { createHash, generateKeyPairSync, sign, verify } from "node:crypto";
import { describe, expect, it } from "vitest";
import { signFunctionsRequest, verifyFunctionsRequest } from "../src/functions-auth";
const keys = generateKeyPairSync("ed25519");
const privateKey = keys.privateKey.export({ type: "pkcs8", format: "pem" }).toString();
const publicKey = keys.publicKey.export({ type: "spki", format: "pem" }).toString();
const now = 1793026000000;
const url = "http://fs-free-1:3456/multi?ids=connection-1&fullEvents=true";
const body = '{"workspaceId":"tenant-1","event":{"type":"track","event":"日本語"}}';
const bytes = Buffer.from(body);
const headers = signFunctionsRequest(privateKey, "free-1", url, body, now);
const request = () => new Request(url, { method: "POST", headers });
describe("functions request authentication", () => {
it("accepts an authentic request with the exact wire body", () => {
expect(verifyFunctionsRequest(publicKey, "free-1", request(), bytes, now)).toBe(true);
});
it.each([
"body",
"deployment",
"method",
"path",
"query",
"signature",
"expired",
"future",
"missing-key",
"invalid-key",
])("rejects %s tampering", change => {
let req = request();
if (change === "method") req = new Request(url, { method: "DELETE", headers });
if (change === "path") req = new Request(url.replace("/multi", "/udfrun"), { method: "POST", headers });
if (change === "query") req = new Request(url.replace("connection-1", "connection-2"), { method: "POST", headers });
if (change === "signature") req.headers.set("x-jitsu-request-signature", "A".repeat(86) + "==");
expect(
verifyFunctionsRequest(
change === "missing-key" ? undefined : change === "invalid-key" ? "invalid" : publicKey,
change === "deployment" ? "other-tenant" : "free-1",
req,
change === "body" ? Buffer.from(body.replace("tenant-1", "tenant-2")) : bytes,
now + (change === "expired" ? 60001 : change === "future" ? -60001 : 0)
)
).toBe(false);
});
it("rejects unsigned calls", () => {
expect(verifyFunctionsRequest(publicKey, "free-1", new Request(url, { method: "POST" }), bytes, now)).toBe(false);
});
it("fails closed when the caller has no key or deployment", () => {
expect(() => signFunctionsRequest(undefined, "free-1", url, body)).toThrow("not configured");
expect(() => signFunctionsRequest(privateKey, "", url, body)).toThrow("not configured");
});
it.each(["", body, "emoji: 🚀; unpaired surrogate: \ud800", "x".repeat(100_000)])(
"preserves the existing signature format for body %#",
wireBody => {
const message = Buffer.from(
[
"jitsu-functions-v1",
"POST",
"/multi?ids=connection-1&fullEvents=true",
"free-1",
String(now),
createHash("sha256").update(Buffer.from(wireBody)).digest("hex"),
].join("\n")
);
const oldSignature = sign(null, message, privateKey).toString("base64");
const signed = signFunctionsRequest(privateKey, "free-1", url, wireBody, now);
expect(signed["x-jitsu-request-signature"]).toBe(oldSignature);
expect(verify(null, message, publicKey, Buffer.from(signed["x-jitsu-request-signature"], "base64"))).toBe(true);
expect(
verifyFunctionsRequest(
publicKey,
"free-1",
new Request(url, { method: "POST", headers: signed }),
Buffer.from(wireBody),
now
)
).toBe(true);
}
);
it("uses the supplied key after rotation and rollback", () => {
const rotated = generateKeyPairSync("ed25519");
const rotatedPrivate = rotated.privateKey.export({ type: "pkcs8", format: "pem" }).toString();
const rotatedPublic = rotated.publicKey.export({ type: "spki", format: "pem" }).toString();
for (const [secret, currentPublic, otherPublic] of [
[privateKey, publicKey, rotatedPublic],
[rotatedPrivate, rotatedPublic, publicKey],
[privateKey, publicKey, rotatedPublic],
]) {
const signed = signFunctionsRequest(secret, "free-1", url, body, now);
const req = new Request(url, { method: "POST", headers: signed });
expect(verifyFunctionsRequest(currentPublic, "free-1", req, bytes, now)).toBe(true);
expect(verifyFunctionsRequest(otherPublic, "free-1", req, bytes, now)).toBe(false);
expect(verifyFunctionsRequest(currentPublic, "free-1", req, bytes, now)).toBe(true);
}
});
it("does not fall back to a cached key when the supplied key is invalid or missing", () => {
signFunctionsRequest(privateKey, "free-1", url, body, now);
expect(verifyFunctionsRequest(publicKey, "free-1", request(), bytes, now)).toBe(true);
for (const invalidKey of [undefined, "", "invalid", "invalid"]) {
expect(() => signFunctionsRequest(invalidKey, "free-1", url, body, now)).toThrow();
expect(verifyFunctionsRequest(invalidKey, "free-1", request(), bytes, now)).toBe(false);
}
expect(signFunctionsRequest(privateKey, "free-1", url, body, now)).toEqual(headers);
expect(verifyFunctionsRequest(publicKey, "free-1", request(), bytes, now)).toBe(true);
});
});