From 3b0593d5e93d83aa62ff60bf82e3184418f95afe Mon Sep 17 00:00:00 2001 From: Vladimir Klimontovich Date: Thu, 9 Jul 2026 12:25:07 +0300 Subject: [PATCH] feat(webhook): optional request signing (HMAC or Ed25519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add optional signing to the webhook destination via a `signatureMethod` choice: none (default), hmac, or ed25519. - hmac: symmetric HMAC-SHA256 with a shared secret (Stripe/GitHub style). - ed25519: asymmetric signature — Jitsu signs with a private key, the receiver verifies with the public key and cannot forge messages. The private key is accepted as PEM or a bare base64 body. Both emit the hex signature in a configurable header (default `Jitsu-Signature`, validated as a legal HTTP header name). Replay protection is on by default: the unix-seconds timestamp is folded into the signed payload (`.`) and sent in a companion `
-Timestamp` header; it can be turned off to sign the body only. Misconfigurations fail as NoRetryError instead of retrying forever: a malformed key/secret, or a method selected with no key. Signing covers stream-mode delivery only; batch mode (Bulker) is unsigned, warned in both the destination form and the connection editor. The console form shows only the fields relevant to the selected method, and gains a reusable `placeholder` prop for config fields. --- .../__tests__/webhook-destination.test.ts | 120 +++++++ libs/destination-functions/package.json | 1 + .../src/functions/webhook-destination.ts | 54 ++- libs/destination-functions/src/meta.ts | 35 ++ pnpm-lock.yaml | 335 +++++++++++++++++- .../ConfigObjectEditor/ConfigEditor.tsx | 3 +- .../components/ConfigObjectEditor/Editors.tsx | 14 +- .../ConnectionEditorPage.tsx | 27 +- webapps/console/lib/schema/destinations.tsx | 23 +- 9 files changed, 581 insertions(+), 31 deletions(-) create mode 100644 libs/destination-functions/__tests__/webhook-destination.test.ts diff --git a/libs/destination-functions/__tests__/webhook-destination.test.ts b/libs/destination-functions/__tests__/webhook-destination.test.ts new file mode 100644 index 000000000..68291e237 --- /dev/null +++ b/libs/destination-functions/__tests__/webhook-destination.test.ts @@ -0,0 +1,120 @@ +import { createHmac, generateKeyPairSync, verify } from "crypto"; +import { afterAll, afterEach, beforeAll, expect, test } from "vitest"; +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; +import { AnalyticsServerEvent } from "@jitsu/protocols/analytics"; +import { testJitsuFunction } from "./lib/testing-lib"; +import WebhookDestination from "../src/functions/webhook-destination"; +import { WebhookDestinationConfig } from "../src/meta"; + +// MSW intercepts the real fetch so we assert on the actual signed request. +const url = "http://webhook.test.local/hook"; +const secret = "super-secret-key"; +const server = setupServer(); + +beforeAll(() => server.listen({ onUnhandledRequest: "error" })); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +// Runs the webhook destination once and returns the signature headers + sent body. +async function sign( + config: Partial +): Promise<{ signature: string; timestamp: string | null; body: string }> { + let captured: { signature: string; timestamp: string | null; body: string } | undefined; + server.use( + http.post(url, async ({ request }) => { + captured = { + signature: request.headers.get("Jitsu-Signature") ?? "", + timestamp: request.headers.get("Jitsu-Signature-Timestamp"), + body: await request.text(), + }; + return HttpResponse.text("ok"); + }) + ); + await testJitsuFunction({ + func: WebhookDestination, + config: { url, method: "POST", ...config } as WebhookDestinationConfig, + events: [{ type: "track", event: "test_event", messageId: "m1" } as AnalyticsServerEvent], + }); + if (!captured) throw new Error("webhook was never called"); + return captured; +} + +test("hmac: signs timestamp + body and sends a timestamp header by default", async () => { + const { signature, timestamp, body } = await sign({ signatureMethod: "hmac", signatureSecret: secret }); + expect(timestamp).toMatch(/^\d+$/); + expect(signature).toBe(createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex")); +}); + +test("hmac: signs the body only and omits the timestamp header when replay protection is off", async () => { + const { signature, timestamp, body } = await sign({ + signatureMethod: "hmac", + signatureSecret: secret, + signatureIncludeTimestamp: false, + }); + expect(timestamp).toBeNull(); + expect(signature).toBe(createHmac("sha256", secret).update(body).digest("hex")); +}); + +test("ed25519: signature verifies against the public key", async () => { + const { publicKey, privateKey } = generateKeyPairSync("ed25519"); + const privatePem = privateKey.export({ type: "pkcs8", format: "pem" }).toString(); + + const { signature, timestamp, body } = await sign({ + signatureMethod: "ed25519", + signaturePrivateKey: privatePem, + }); + + expect(timestamp).toMatch(/^\d+$/); + const ok = verify(null, Buffer.from(`${timestamp}.${body}`), publicKey, Buffer.from(signature, "hex")); + expect(ok).toBe(true); +}); + +test("a signing method with no key configured fails without retrying", async () => { + server.use(http.post(url, () => HttpResponse.text("ok"))); + await expect( + testJitsuFunction({ + func: WebhookDestination, + config: { url, method: "POST", signatureMethod: "hmac" } as WebhookDestinationConfig, + events: [{ type: "track", event: "test_event", messageId: "m1" } as AnalyticsServerEvent], + }) + ).rejects.toMatchObject({ name: "NoRetryError" }); +}); + +test("schema rejects an invalid signature header name", () => { + expect( + WebhookDestinationConfig.safeParse({ url: "https://example.com", signatureHeader: "Bad Header" }).success + ).toBe(false); + expect( + WebhookDestinationConfig.safeParse({ url: "https://example.com", signatureHeader: "X-My-Signature" }).success + ).toBe(true); +}); + +test("ed25519: a malformed private key fails without retrying", async () => { + server.use(http.post(url, () => HttpResponse.text("ok"))); + await expect( + testJitsuFunction({ + func: WebhookDestination, + config: { + url, + method: "POST", + signatureMethod: "ed25519", + signaturePrivateKey: "not-a-real-key", + } as WebhookDestinationConfig, + events: [{ type: "track", event: "test_event", messageId: "m1" } as AnalyticsServerEvent], + }) + ).rejects.toMatchObject({ name: "NoRetryError" }); +}); + +test("ed25519: accepts a bare base64 key body with surrounding whitespace", async () => { + const { publicKey, privateKey } = generateKeyPairSync("ed25519"); + const bareBase64 = privateKey.export({ type: "pkcs8", format: "der" }).toString("base64"); + + const { signature, timestamp, body } = await sign({ + signatureMethod: "ed25519", + signaturePrivateKey: `\n ${bareBase64} \n`, + }); + + const ok = verify(null, Buffer.from(`${timestamp}.${body}`), publicKey, Buffer.from(signature, "hex")); + expect(ok).toBe(true); +}); diff --git a/libs/destination-functions/package.json b/libs/destination-functions/package.json index 3d72f7427..45573cd3a 100644 --- a/libs/destination-functions/package.json +++ b/libs/destination-functions/package.json @@ -28,6 +28,7 @@ "@types/node": "catalog:", "@vitest/ui": "catalog:", "json5": "^2.1.0", + "msw": "^2.15.0", "vitest": "catalog:", "vite": "^6.4.3" }, diff --git a/libs/destination-functions/src/functions/webhook-destination.ts b/libs/destination-functions/src/functions/webhook-destination.ts index 9edce5fa2..fcf38333e 100644 --- a/libs/destination-functions/src/functions/webhook-destination.ts +++ b/libs/destination-functions/src/functions/webhook-destination.ts @@ -1,5 +1,6 @@ +import { createHmac, createPrivateKey, sign } from "crypto"; import { JitsuFunction } from "@jitsu/protocols/functions"; -import { HTTPError, RetryError } from "@jitsu/functions-lib"; +import { HTTPError, NoRetryError, NoRetryErrorName, RetryError } from "@jitsu/functions-lib"; import type { AnalyticsServerEvent } from "@jitsu/protocols/analytics"; import { WebhookDestinationConfig } from "../meta"; import { MetricsMeta } from "@jitsu/core-functions-lib"; @@ -10,6 +11,21 @@ const bulkerAuthKey = process.env.BULKER_AUTH_KEY; const macrosPattern = /\{\{\s*([\w.-]+)\s*}}/g; +// Accepts an Ed25519 private key as full PEM or as the bare base64 PKCS#8 body +// (with any surrounding whitespace), returning a PEM string createPrivateKey parses. +function toPrivateKeyPem(input: string): string { + const trimmed = input.trim(); + if (trimmed.includes("-----BEGIN")) { + return trimmed; + } + const body = + trimmed + .replace(/\s+/g, "") + .match(/.{1,64}/g) + ?.join("\n") ?? trimmed; + return `-----BEGIN PRIVATE KEY-----\n${body}\n-----END PRIVATE KEY-----`; +} + const WebhookDestination: JitsuFunction = async (event, ctx) => { if (ctx["connectionOptions"]?.mode === "batch" && bulkerBase) { const metricsMeta: Omit = { @@ -79,6 +95,38 @@ const WebhookDestination: JitsuFunction = {}; + const method = ctx.props.signatureMethod || "none"; + if (method !== "none") { + const secret = method === "hmac" ? ctx.props.signatureSecret : undefined; + const privateKey = method === "ed25519" ? ctx.props.signaturePrivateKey : undefined; + if (!secret && !privateKey) { + // Method selected but no key — fail loudly instead of silently sending unsigned. + throw new NoRetryError( + `Webhook signing method "${method}" is enabled but no ${ + method === "hmac" ? "secret" : "private key" + } is configured` + ); + } + const headerName = ctx.props.signatureHeader || "Jitsu-Signature"; + let signedData = payload; + let timestamp: string | undefined; + if (ctx.props.signatureIncludeTimestamp ?? true) { + timestamp = String(Math.floor(Date.now() / 1000)); + signedData = `${timestamp}.${payload}`; + } + try { + signatureHeaders[headerName] = secret + ? createHmac("sha256", secret).update(signedData).digest("hex") + : sign(null, Buffer.from(signedData), createPrivateKey(toPrivateKeyPem(privateKey!))).toString("hex"); + } catch (e: any) { + // A malformed key/secret can never succeed on retry — drop instead of retrying forever. + throw new NoRetryError(`Webhook signing failed, check the signing key/secret: ${e.message}`); + } + if (timestamp) { + signatureHeaders[`${headerName}-Timestamp`] = timestamp; + } + } const res = await ctx.fetch(ctx.props.url, { method: ctx.props.method || "POST", body: payload, @@ -88,6 +136,7 @@ const WebhookDestination: JitsuFunction
  • {{ EVENT }} - event json object for stream mode or batches with size=1
  • {{ EVENTS }} - for batch mode - json array of events
  • {{ EVENTS_COUNT }} - count of events in batch
  • {{ NAME }} - event name
  • {{ env.VAR_NAME }} - value of VAR_NAME environment variable
  • " ), + signatureMethod: z + .enum(["none", "hmac", "ed25519"]) + .optional() + .default("none") + .describe( + "Signature Method::How outgoing requests are signed so your endpoint can verify them.
    • none — requests are not signed.
    • hmac — symmetric HMAC-SHA256 with a shared secret (like Stripe/GitHub). Simple, but anyone who can verify can also forge, so only use it for endpoints you fully trust.
    • ed25519 — asymmetric signature. Jitsu signs with a private key; your endpoint verifies with the public key and cannot forge messages. Prefer this when many or untrusted endpoints receive the webhook.
    Note: signing applies to stream-mode delivery only. In batch mode events are delivered by Bulker and are not signed — use stream mode if you need signed webhooks." + ), + signatureSecret: z + .string() + .optional() + .describe( + "Signing Secret::Shared secret for HMAC-SHA256 signing (symmetric — there is no key pair). Put the same value here and in your receiving endpoint. Generate a random one with openssl rand -hex 32 (any high-entropy string of 32+ bytes works). To verify a request, your endpoint recomputes the HMAC over the signed payload with this secret and compares the hex digest to the Jitsu-Signature header using a constant-time comparison. Keep it secret." + ), + signaturePrivateKey: z + .string() + .optional() + .describe( + "Private Key (PEM)::Ed25519 private key used to sign requests. Generate a key pair:
    openssl genpkey -algorithm ed25519 -out jitsu_private.pem
    openssl pkey -in jitsu_private.pem -pubout -out jitsu_public.pem
    Paste the contents of jitsu_private.pem here and install jitsu_public.pem on your endpoint. To verify a request, your endpoint checks the Jitsu-Signature header (a hex Ed25519 signature) against the signed payload using the public key. Jitsu holds only the private key — keep it secret; the public key is safe to distribute." + ), + signatureHeader: z + .string() + .regex( + /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/, + "Must be a valid HTTP header name (letters, digits, and !#$%&'*+-.^_`|~ only)" + ) + .optional() + .default("Jitsu-Signature") + .describe("Signature Header::Name of the header carrying the signature. Only used when signing is enabled."), + signatureIncludeTimestamp: z + .boolean() + .optional() + .default(true) + .describe( + "Replay Protection::When enabled (recommended), the request timestamp (unix seconds) is folded into the signed payload — the signature covers <timestamp>.<body> — and sent in a companion <Signature Header>-Timestamp header. Your endpoint can then reject requests with an old timestamp to block replays of a captured, still-valid request. When disabled, only the raw body is signed and no timestamp header is sent: simpler to verify, but a captured request stays valid forever. Only used when signing is enabled." + ), }); export type WebhookDestinationConfig = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 188724e58..785db1c70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,7 +227,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) dev-scripts: dependencies: @@ -311,7 +311,7 @@ importers: version: 6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@26.0.0)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@26.0.0)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@26.0.0)(typescript@5.6.3))(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) libs/core-functions-lib: dependencies: @@ -372,7 +372,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) libs/destination-functions: dependencies: @@ -431,12 +431,15 @@ importers: json5: specifier: ^2.1.0 version: 2.2.3 + msw: + specifier: ^2.15.0 + version: 2.15.0(@types/node@18.19.61)(typescript@5.6.3) vite: specifier: ^6.4.3 version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) libs/functions: devDependencies: @@ -469,7 +472,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) libs/jitsu-js: dependencies: @@ -536,7 +539,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) libs/jitsu-react: dependencies: @@ -646,7 +649,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) services/rotor: dependencies: @@ -800,7 +803,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) types/protocols: devDependencies: @@ -1146,7 +1149,7 @@ importers: version: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) zod-prisma: specifier: ^0.5.4 version: 0.5.4(decimal.js@10.6.0)(prisma@6.19.3(typescript@5.6.3))(zod@3.25.76) @@ -2399,6 +2402,28 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -2412,6 +2437,19 @@ packages: resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} engines: {node: '>=18'} + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} @@ -2594,6 +2632,10 @@ packages: resolution: {integrity: sha512-mQ2s0pYYiav+tzCDR05Zptem8Ey2v8s11lri5RKGhTtL4COVCvVCk5vtyRYNT+9L8qSfyOqqefF9UtnW8mC5jA==} engines: {node: '>= 20.19.0'} + '@mswjs/interceptors@0.41.9': + resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} + engines: {node: '>=18'} + '@nangohq/frontend@0.21.17': resolution: {integrity: sha512-utnO6JkRIQVqZBo472Yynf0nBl5Pc3QCMlC9L5LsIWApCD1pSkqZCA7cZKIJiX2p3tiB8Hzav31o0lAsnTZ1AQ==} @@ -2673,6 +2715,18 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -4021,6 +4075,9 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + '@types/ssh2-streams@0.1.12': resolution: {integrity: sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==} @@ -4033,6 +4090,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} @@ -5065,6 +5125,10 @@ packages: resolution: {integrity: sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==} engines: {node: '>=18'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -5963,12 +6027,21 @@ packages: fast-shallow-equal@1.0.0: resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-text-encoding@1.0.6: resolution: {integrity: sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==} fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fast-xml-builder@1.2.0: resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} @@ -6312,6 +6385,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gtoken@5.3.2: resolution: {integrity: sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==} engines: {node: '>=10'} @@ -6363,6 +6440,9 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -6699,6 +6779,9 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -7603,10 +7686,24 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.15.0: + resolution: {integrity: sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -7887,6 +7984,9 @@ packages: resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -7992,6 +8092,9 @@ packages: path-to-regexp@0.1.13: resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -8792,6 +8895,9 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + rettime@0.11.11: + resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -8906,6 +9012,9 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-cookie-parser@3.1.1: + resolution: {integrity: sha512-vM9SUhjsUYs6UeJUmygc5Ofm5eQGe85riob5ju6XCgFGJI5PLV4nrDAQpQjd+LkFBpAkADn5BQQpZ9EUNkyLuA==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -9121,6 +9230,9 @@ packages: streamx@2.25.0: resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-convert@0.2.1: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} @@ -9416,6 +9528,13 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tldts-core@7.4.7: + resolution: {integrity: sha512-rNlAI8fKn/JckBMUSbNL/ES2kmDiurWaE49l+ikwEc9A6lFR7gMx9AhgQMQKBK4H5w4pKLH64JzZfB99uRsGNQ==} + + tldts@7.4.7: + resolution: {integrity: sha512-56L0/9HELHSsG1bFCzay8UoLxzRL7kpFf7Wl5q/kSYwiSJGACvro61xnKzPNM+SadxllzdtXsKDSXE7HPeqIAw==} + hasBin: true + tmp@0.2.7: resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} @@ -9446,6 +9565,10 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -9670,6 +9793,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -11706,6 +11832,48 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@inquirer/ansi@2.0.7': {} + + '@inquirer/confirm@6.1.1(@types/node@18.19.61)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@18.19.61) + '@inquirer/type': 4.0.7(@types/node@18.19.61) + optionalDependencies: + '@types/node': 18.19.61 + + '@inquirer/confirm@6.1.1(@types/node@26.0.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@26.0.0) + '@inquirer/type': 4.0.7(@types/node@26.0.0) + optionalDependencies: + '@types/node': 26.0.0 + optional: true + + '@inquirer/core@11.2.1(@types/node@18.19.61)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@18.19.61) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 18.19.61 + + '@inquirer/core@11.2.1(@types/node@26.0.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@26.0.0) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 26.0.0 + optional: true + '@inquirer/external-editor@1.0.3(@types/node@18.19.61)': dependencies: chardet: 2.1.1 @@ -11715,6 +11883,17 @@ snapshots: '@inquirer/figures@1.0.7': {} + '@inquirer/figures@2.0.7': {} + + '@inquirer/type@4.0.7(@types/node@18.19.61)': + optionalDependencies: + '@types/node': 18.19.61 + + '@inquirer/type@4.0.7(@types/node@26.0.0)': + optionalDependencies: + '@types/node': 26.0.0 + optional: true + '@ioredis/commands@1.5.1': {} '@isaacs/cliui@8.0.2': @@ -12066,6 +12245,15 @@ snapshots: node-addon-api: 8.5.0 prebuild-install: 7.1.3 + '@mswjs/interceptors@0.41.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@nangohq/frontend@0.21.17': {} '@next/bundle-analyzer@16.2.6': @@ -12121,6 +12309,17 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@opentelemetry/api@1.9.0': {} '@panva/hkdf@1.2.1': {} @@ -13635,6 +13834,10 @@ snapshots: '@types/node': 18.19.61 '@types/send': 0.17.4 + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 26.0.0 + '@types/ssh2-streams@0.1.12': dependencies: '@types/node': 18.19.61 @@ -13650,6 +13853,8 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/statuses@2.0.6': {} + '@types/through@0.0.33': dependencies: '@types/node': 18.19.61 @@ -13878,20 +14083,22 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: + msw: 2.15.0(@types/node@18.19.61)(typescript@5.6.3) vite: 6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.1.4(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.15.0(@types/node@26.0.0)(typescript@5.6.3))(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: + msw: 2.15.0(@types/node@26.0.0)(typescript@5.6.3) vite: 6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@4.1.4': @@ -13921,7 +14128,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils@4.1.4': dependencies: @@ -14913,6 +15120,8 @@ snapshots: cookie@1.0.1: {} + cookie@1.1.1: {} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 @@ -16140,10 +16349,20 @@ snapshots: fast-shallow-equal@1.0.0: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-text-encoding@1.0.6: {} fast-uri@3.1.2: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fast-xml-builder@1.2.0: dependencies: path-expression-matcher: 1.5.0 @@ -16660,6 +16879,8 @@ snapshots: graceful-fs@4.2.11: {} + graphql@16.14.2: {} + gtoken@5.3.2(encoding@0.1.13): dependencies: gaxios: 4.3.3(encoding@0.1.13) @@ -16718,6 +16939,11 @@ snapshots: dependencies: function-bind: 1.1.2 + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.1 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -17080,6 +17306,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -18576,8 +18804,61 @@ snapshots: ms@2.1.3: {} + msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3): + dependencies: + '@inquirer/confirm': 6.1.1(@types/node@18.19.61) + '@mswjs/interceptors': 0.41.9 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.14.2 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.11 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.2 + type-fest: 5.5.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - '@types/node' + + msw@2.15.0(@types/node@26.0.0)(typescript@5.6.3): + dependencies: + '@inquirer/confirm': 6.1.1(@types/node@26.0.0) + '@mswjs/interceptors': 0.41.9 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.14.2 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.11 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.2 + type-fest: 5.5.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - '@types/node' + optional: true + mute-stream@1.0.0: {} + mute-stream@3.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -18884,6 +19165,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 + outvariant@1.4.3: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -18981,6 +19264,8 @@ snapshots: path-to-regexp@0.1.13: {} + path-to-regexp@6.3.0: {} + path-to-regexp@8.4.2: {} path-type@4.0.0: @@ -20045,6 +20330,8 @@ snapshots: retry@0.13.1: optional: true + rettime@0.11.11: {} + reusify@1.0.4: {} rollup@4.62.2: @@ -20230,6 +20517,8 @@ snapshots: set-cookie-parser@2.7.2: {} + set-cookie-parser@3.1.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -20507,6 +20796,8 @@ snapshots: - bare-abort-controller - react-native-b4a + strict-event-emitter@0.5.1: {} + string-convert@0.2.1: {} string-length@4.0.2: @@ -20884,6 +21175,12 @@ snapshots: tinyrainbow@3.1.0: {} + tldts-core@7.4.7: {} + + tldts@7.4.7: + dependencies: + tldts-core: 7.4.7 + tmp@0.2.7: {} tmpl@1.0.5: {} @@ -20908,6 +21205,10 @@ snapshots: url-parse: 1.5.10 optional: true + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.7 + tr46@0.0.3: {} tr46@2.1.0: @@ -21156,6 +21457,8 @@ snapshots: unpipe@1.0.0: {} + until-async@3.0.2: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -21295,10 +21598,10 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@18.19.61)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(msw@2.15.0(@types/node@18.19.61)(typescript@5.6.3))(vite@6.4.3(@types/node@18.19.61)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -21325,10 +21628,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@26.0.0)(@vitest/ui@4.1.4)(jsdom@16.7.0)(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@26.0.0)(@vitest/ui@4.1.4)(jsdom@16.7.0)(msw@2.15.0(@types/node@26.0.0)(typescript@5.6.3))(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(msw@2.15.0(@types/node@26.0.0)(typescript@5.6.3))(vite@6.4.3(@types/node@26.0.0)(jiti@2.6.1)(less@4.6.4)(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 diff --git a/webapps/console/components/ConfigObjectEditor/ConfigEditor.tsx b/webapps/console/components/ConfigObjectEditor/ConfigEditor.tsx index 968b2ed36..bc226f2f4 100644 --- a/webapps/console/components/ConfigObjectEditor/ConfigEditor.tsx +++ b/webapps/console/components/ConfigObjectEditor/ConfigEditor.tsx @@ -177,6 +177,7 @@ export type FieldDisplay = { correction?: any | ((a: any, isNew?: boolean) => any); textarea?: boolean; password?: boolean; + placeholder?: string; }; export type EditorComponentFactory = (props: EditorComponentProps) => React.FC | undefined; @@ -247,7 +248,7 @@ function getUiSchema(schema: JsonSchema, fields: Record, o const fieldProps = { "ui:widget": getUiWidget(field, object, isNew), "ui:disabled": field?.constant ? true : undefined, - "ui:placeholder": field?.constant, + "ui:placeholder": field?.placeholder ?? field?.constant, "ui:title": field?.displayName || createDisplayName(name), "ui:FieldTemplate": FieldTemplate, "ui:ObjectFieldTemplate": NestedObjectTemplate, diff --git a/webapps/console/components/ConfigObjectEditor/Editors.tsx b/webapps/console/components/ConfigObjectEditor/Editors.tsx index 21ca83fa4..7d20acf6a 100644 --- a/webapps/console/components/ConfigObjectEditor/Editors.tsx +++ b/webapps/console/components/ConfigObjectEditor/Editors.tsx @@ -107,7 +107,9 @@ export const NumberEditor: React.FC & { ma ); }; -export const PasswordEditor: React.FC & { rows?: number; options?: any }> = props => { +export const PasswordEditor: React.FC< + CustomWidgetProps & { rows?: number; options?: any; placeholder?: string } +> = props => { const userRole = useWorkspaceRole(); const canEdit = !props.disabled && userRole.editEntities; @@ -153,9 +155,7 @@ export const PasswordEditor: React.FC & { rows?: numbe className={!isEditMode || !isPasswordVisible ? styles.passwordTextareaMasked : styles.passwordTextareaVisible} placeholder={ canEdit && isEditMode - ? isMasked - ? "Enter new password or secret..." - : "Enter password or secret..." + ? props.placeholder || (isMasked ? "Enter new password or secret..." : "Enter password or secret...") : undefined } disabled={props.disabled || !canEdit || !isEditMode} @@ -199,7 +199,11 @@ export const PasswordEditor: React.FC & { rows?: numbe onChange={e => handleChange(e.target.value)} visibilityToggle={canEdit && isEditMode} disabled={props.disabled || !canEdit || !isEditMode} - placeholder={canEdit && isEditMode ? (isMasked ? "Enter new password..." : "Enter password...") : undefined} + placeholder={ + canEdit && isEditMode + ? props.placeholder || (isMasked ? "Enter new password..." : "Enter password...") + : undefined + } className={styles.passwordInput} /> {canEdit && isMasked && !isEditMode && ( diff --git a/webapps/console/components/ConnectionEditorPage/ConnectionEditorPage.tsx b/webapps/console/components/ConnectionEditorPage/ConnectionEditorPage.tsx index b7dd0e9c2..94076c210 100644 --- a/webapps/console/components/ConnectionEditorPage/ConnectionEditorPage.tsx +++ b/webapps/console/components/ConnectionEditorPage/ConnectionEditorPage.tsx @@ -6,7 +6,7 @@ import { SomeZodObject, z } from "zod"; import { ConfigurationObjectLinkDbModel } from "../../prisma/schema"; import { useRouter } from "next/router"; import { assertTrue, getLog, requireDefined } from "juava"; -import { Button, Input, InputNumber, Radio, Switch, Tooltip } from "antd"; +import { Alert, Button, Input, InputNumber, Radio, Switch, Tooltip } from "antd"; import { BaseBulkerConnectionOptions, getCoreDestinationType } from "../../lib/schema/destinations"; import { confirmOp, copyTextToClipboard, feedbackError, feedbackSuccess } from "../../lib/ui"; import FieldListEditorLayout, { EditorItem } from "../FieldListEditorLayout/FieldListEditorLayout"; @@ -342,12 +342,25 @@ function ConnectionEditor({ ), component: ( - updateOptions({ mode })} - /> + <> + updateOptions({ mode })} + /> + {destinationType.id === "webhook" && + destination.signatureMethod && + destination.signatureMethod !== "none" && + connectionOptions.mode === "batch" && ( + + )} + ), }); } diff --git a/webapps/console/lib/schema/destinations.tsx b/webapps/console/lib/schema/destinations.tsx index 32bb47299..ac24ee877 100644 --- a/webapps/console/lib/schema/destinations.tsx +++ b/webapps/console/lib/schema/destinations.tsx @@ -79,6 +79,10 @@ export type PropertyUI = { * If string field should be treated as password */ password?: boolean; + /** + * Placeholder shown in the input when the field is empty + */ + placeholder?: string; /** * If the field should not be displayed. That field must have a default value */ @@ -1128,9 +1132,26 @@ export const coreDestinations: DestinationType[] = [ editorProps: { languages: ["json", "text"], height: "300", syntaxCheck: { json: false } }, hidden: obj => !obj.customPayload, }, + signatureSecret: { + password: true, + placeholder: "Enter secret for this destination", + hidden: obj => obj.signatureMethod !== "hmac", + }, + signaturePrivateKey: { + password: true, + textarea: true, + placeholder: "-----BEGIN PRIVATE KEY-----", + hidden: obj => obj.signatureMethod !== "ed25519", + }, + signatureHeader: { + hidden: obj => !obj.signatureMethod || obj.signatureMethod === "none", + }, + signatureIncludeTimestamp: { + hidden: obj => !obj.signatureMethod || obj.signatureMethod === "none", + }, }, description: - "Send data to any HTTP endpoint. You can use this destination to send data to Slack, Discord, or any other service that accepts HTTP requests. ", + "Send data to any HTTP endpoint. You can use this destination to send data to Slack, Discord, or any other service that accepts HTTP requests. Requests can optionally be signed (HMAC-SHA256 or Ed25519) so your endpoint can verify their authenticity.", }, ];