Ctrl K
ComunidadGuías…

libs/juava/__tests__/functions-auth.test.ts

Rama newjitsu · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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);
  });
});