Revert "fix: add input validation to analytics app schemas (#26976)"
This reverts commit c43c48b1a8.
This commit is contained in:
@@ -48,11 +48,11 @@ export function createAppsFixture(page: Page) {
|
||||
|
||||
await page.click(`[data-testid="save-event-types"]`);
|
||||
|
||||
// adding valid GTM container ID to gtm-tracking-id-input because this field is required and the test fails without it
|
||||
// adding random-tracking-id to gtm-tracking-id-input because this field is required and the test fails without it
|
||||
if (app === "gtm") {
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
for (let index = 0; index < eventTypeIds.length; index++) {
|
||||
await page.getByTestId("gtm-tracking-id-input").nth(index).fill("GTM-ABC123");
|
||||
await page.getByTestId("gtm-tracking-id-input").nth(index).fill("random-tracking-id");
|
||||
}
|
||||
}
|
||||
await page.click(`[data-testid="configure-step-save"]`);
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const safeUrlSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine(
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
try {
|
||||
const url = new URL(val);
|
||||
return url.protocol === "http:" || url.protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: "Invalid URL format. Must be a valid http or https URL" }
|
||||
);
|
||||
|
||||
// Schema for tracking IDs that should only contain letters, numbers, underscores, and hyphens
|
||||
export const alphanumericIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[A-Za-z0-9_-]+$/.test(val), {
|
||||
message: "Invalid ID format. Expected alphanumeric characters, underscores, or hyphens",
|
||||
});
|
||||
|
||||
// Schema for tracking IDs that should only contain digits
|
||||
export const numericIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[0-9]+$/.test(val), {
|
||||
message: "Invalid ID format. Expected a numeric ID",
|
||||
});
|
||||
|
||||
// Factory for creating prefixed ID schemas (GTM, GA4, Fathom, etc.)
|
||||
export const createPrefixedIdSchema = (options: {
|
||||
prefix?: string;
|
||||
addPrefixIfMissing?: boolean;
|
||||
allowEmpty?: boolean;
|
||||
}) => {
|
||||
const { prefix = "", addPrefixIfMissing = false, allowEmpty = true } = options;
|
||||
|
||||
return z
|
||||
.string()
|
||||
.transform((val) => {
|
||||
let result = val.trim().toUpperCase();
|
||||
if (prefix && addPrefixIfMissing) {
|
||||
const clean = result.replace(new RegExp(`^${prefix}`, "i"), "");
|
||||
result = `${prefix}${clean}`;
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.refine(
|
||||
(val) => {
|
||||
if (allowEmpty && val === "") return true;
|
||||
const pattern = prefix ? new RegExp(`^${prefix}[A-Z0-9]{1,20}$`) : /^[A-Z0-9]{1,20}$/;
|
||||
return pattern.test(val);
|
||||
},
|
||||
{ message: `Invalid ID format${prefix ? `. Expected: ${prefix}XXXXXX` : ""}` }
|
||||
);
|
||||
};
|
||||
@@ -1,255 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { appDataSchema as databuddySchema } from "./databuddy/zod";
|
||||
import { appDataSchema as fathomSchema } from "./fathom/zod";
|
||||
import { appDataSchema as ga4Schema } from "./ga4/zod";
|
||||
import { appDataSchema as gtmSchema } from "./gtm/zod";
|
||||
import { appDataSchema as insihtsSchema } from "./insihts/zod";
|
||||
import { appDataSchema as matomoSchema } from "./matomo/zod";
|
||||
import { appDataSchema as metapixelSchema } from "./metapixel/zod";
|
||||
import { appDataSchema as plausibleSchema } from "./plausible/zod";
|
||||
import { appDataSchema as posthogSchema } from "./posthog/zod";
|
||||
import { appDataSchema as twiplaSchema } from "./twipla/zod";
|
||||
import { appDataSchema as umamiSchema } from "./umami/zod";
|
||||
|
||||
// Common XSS payloads that should be rejected by all schemas
|
||||
const xssPayloads = [
|
||||
"';alert(1)//",
|
||||
'"><script>alert(1)</script>',
|
||||
"javascript:alert(1)",
|
||||
"<img src=x onerror=alert(1)>",
|
||||
"' onclick=alert(1) data-x='",
|
||||
];
|
||||
|
||||
describe("Analytics Apps - Input Validation", () => {
|
||||
describe("GTM", () => {
|
||||
it("accepts valid GTM container IDs", () => {
|
||||
expect(gtmSchema.parse({ trackingId: "GTM-ABC123" }).trackingId).toBe("GTM-ABC123");
|
||||
expect(gtmSchema.parse({ trackingId: "abc123" }).trackingId).toBe("GTM-ABC123");
|
||||
expect(gtmSchema.parse({ trackingId: "gtm-xyz789" }).trackingId).toBe("GTM-XYZ789");
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => gtmSchema.parse({ trackingId: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("GA4", () => {
|
||||
it("accepts valid GA4 measurement IDs", () => {
|
||||
expect(ga4Schema.parse({ trackingId: "G-ABC1234567" }).trackingId).toBe("G-ABC1234567");
|
||||
expect(ga4Schema.parse({ trackingId: "g-abc1234567" }).trackingId).toBe("G-ABC1234567");
|
||||
expect(ga4Schema.parse({ trackingId: "" }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => ga4Schema.parse({ trackingId: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Meta Pixel", () => {
|
||||
it("accepts valid pixel IDs (numeric)", () => {
|
||||
expect(metapixelSchema.parse({ trackingId: "1234567890123456" }).trackingId).toBe("1234567890123456");
|
||||
expect(metapixelSchema.parse({ trackingId: "" }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("rejects non-numeric values", () => {
|
||||
expect(() => metapixelSchema.parse({ trackingId: "abc123" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => metapixelSchema.parse({ trackingId: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("PostHog", () => {
|
||||
it("accepts valid PostHog credentials", () => {
|
||||
const result = posthogSchema.parse({
|
||||
TRACKING_ID: "phc_abc123XYZ",
|
||||
API_HOST: "https://app.posthog.com",
|
||||
});
|
||||
expect(result.TRACKING_ID).toBe("phc_abc123XYZ");
|
||||
expect(result.API_HOST).toBe("https://app.posthog.com");
|
||||
});
|
||||
|
||||
it("accepts legacy alphanumeric TRACKING_IDs", () => {
|
||||
expect(posthogSchema.parse({ TRACKING_ID: "legacy_key_123" }).TRACKING_ID).toBe("legacy_key_123");
|
||||
});
|
||||
|
||||
it("rejects javascript: URLs", () => {
|
||||
expect(() => posthogSchema.parse({ API_HOST: "javascript:alert(1)" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => posthogSchema.parse({ TRACKING_ID: payload })).toThrow();
|
||||
expect(() => posthogSchema.parse({ API_HOST: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Fathom", () => {
|
||||
it("accepts valid site IDs", () => {
|
||||
expect(fathomSchema.parse({ trackingId: "ABCDEFG" }).trackingId).toBe("ABCDEFG");
|
||||
expect(fathomSchema.parse({ trackingId: "abcdefg" }).trackingId).toBe("ABCDEFG");
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => fathomSchema.parse({ trackingId: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Plausible", () => {
|
||||
it("accepts valid domain and URL", () => {
|
||||
const result = plausibleSchema.parse({
|
||||
trackingId: "example.com",
|
||||
PLAUSIBLE_URL: "https://plausible.io/js/script.js",
|
||||
});
|
||||
expect(result.trackingId).toBe("example.com");
|
||||
expect(result.PLAUSIBLE_URL).toBe("https://plausible.io/js/script.js");
|
||||
});
|
||||
|
||||
it("accepts valid subdomains", () => {
|
||||
expect(plausibleSchema.parse({ trackingId: "sub.example.com" }).trackingId).toBe("sub.example.com");
|
||||
expect(plausibleSchema.parse({ trackingId: "deep.sub.example.com" }).trackingId).toBe(
|
||||
"deep.sub.example.com"
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts single-label domains", () => {
|
||||
expect(plausibleSchema.parse({ trackingId: "localhost" }).trackingId).toBe("localhost");
|
||||
});
|
||||
|
||||
it("accepts domains with hyphens in labels", () => {
|
||||
expect(plausibleSchema.parse({ trackingId: "my-site.example.com" }).trackingId).toBe(
|
||||
"my-site.example.com"
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects consecutive dots", () => {
|
||||
expect(() => plausibleSchema.parse({ trackingId: "example..com" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects hyphens at label boundaries", () => {
|
||||
expect(() => plausibleSchema.parse({ trackingId: "-example.com" })).toThrow();
|
||||
expect(() => plausibleSchema.parse({ trackingId: "example-.com" })).toThrow();
|
||||
expect(() => plausibleSchema.parse({ trackingId: "example.-com" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => plausibleSchema.parse({ trackingId: payload })).toThrow();
|
||||
expect(() => plausibleSchema.parse({ PLAUSIBLE_URL: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Matomo", () => {
|
||||
it("accepts valid URL and numeric site ID", () => {
|
||||
const result = matomoSchema.parse({
|
||||
MATOMO_URL: "https://matomo.example.com",
|
||||
SITE_ID: "42",
|
||||
});
|
||||
expect(result.MATOMO_URL).toBe("https://matomo.example.com");
|
||||
expect(result.SITE_ID).toBe("42");
|
||||
});
|
||||
|
||||
it("rejects non-numeric SITE_ID", () => {
|
||||
expect(() => matomoSchema.parse({ SITE_ID: "abc" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => matomoSchema.parse({ MATOMO_URL: payload })).toThrow();
|
||||
expect(() => matomoSchema.parse({ SITE_ID: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Umami", () => {
|
||||
it("accepts UUID (v2) and URL", () => {
|
||||
const result = umamiSchema.parse({
|
||||
SITE_ID: "4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b",
|
||||
SCRIPT_URL: "https://umami.example.com/script.js",
|
||||
});
|
||||
expect(result.SITE_ID).toBe("4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b");
|
||||
expect(result.SCRIPT_URL).toBe("https://umami.example.com/script.js");
|
||||
});
|
||||
|
||||
it("accepts numeric ID (v1)", () => {
|
||||
expect(umamiSchema.parse({ SITE_ID: "12345" }).SITE_ID).toBe("12345");
|
||||
});
|
||||
|
||||
it("rejects invalid format", () => {
|
||||
expect(() => umamiSchema.parse({ SITE_ID: "not-a-valid-id!" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => umamiSchema.parse({ SITE_ID: payload })).toThrow();
|
||||
expect(() => umamiSchema.parse({ SCRIPT_URL: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Twipla", () => {
|
||||
it("accepts valid site IDs", () => {
|
||||
expect(twiplaSchema.parse({ SITE_ID: "abc123" }).SITE_ID).toBe("abc123");
|
||||
expect(twiplaSchema.parse({ SITE_ID: "4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b" }).SITE_ID).toBe(
|
||||
"4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b"
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => twiplaSchema.parse({ SITE_ID: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Insihts", () => {
|
||||
it("accepts valid site ID and URL", () => {
|
||||
const result = insihtsSchema.parse({
|
||||
SITE_ID: "site_abc123",
|
||||
SCRIPT_URL: "https://collector.insihts.com/script.js",
|
||||
});
|
||||
expect(result.SITE_ID).toBe("site_abc123");
|
||||
expect(result.SCRIPT_URL).toBe("https://collector.insihts.com/script.js");
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => insihtsSchema.parse({ SITE_ID: payload })).toThrow();
|
||||
expect(() => insihtsSchema.parse({ SCRIPT_URL: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Databuddy", () => {
|
||||
it("accepts valid client ID and URLs", () => {
|
||||
const result = databuddySchema.parse({
|
||||
CLIENT_ID: "client_abc123",
|
||||
DATABUDDY_SCRIPT_URL: "https://cdn.databuddy.cc/databuddy.js",
|
||||
DATABUDDY_API_URL: "https://basket.databuddy.cc",
|
||||
});
|
||||
expect(result.CLIENT_ID).toBe("client_abc123");
|
||||
expect(result.DATABUDDY_SCRIPT_URL).toBe("https://cdn.databuddy.cc/databuddy.js");
|
||||
expect(result.DATABUDDY_API_URL).toBe("https://basket.databuddy.cc");
|
||||
});
|
||||
|
||||
it("rejects XSS payloads", () => {
|
||||
for (const payload of xssPayloads) {
|
||||
expect(() => databuddySchema.parse({ CLIENT_ID: payload })).toThrow();
|
||||
expect(() => databuddySchema.parse({ DATABUDDY_SCRIPT_URL: payload })).toThrow();
|
||||
expect(() => databuddySchema.parse({ DATABUDDY_API_URL: payload })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
|
||||
import { alphanumericIdSchema, safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
DATABUDDY_SCRIPT_URL: safeUrlSchema.optional().default("https://cdn.databuddy.cc/databuddy.js").or(z.undefined()),
|
||||
DATABUDDY_API_URL: safeUrlSchema.optional().default("https://basket.databuddy.cc").or(z.undefined()),
|
||||
CLIENT_ID: alphanumericIdSchema.optional(),
|
||||
DATABUDDY_SCRIPT_URL: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("https://cdn.databuddy.cc/databuddy.js")
|
||||
.or(z.undefined()),
|
||||
DATABUDDY_API_URL: z.string().optional().default("https://basket.databuddy.cc").or(z.undefined()),
|
||||
CLIENT_ID: z.string().default("").optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
|
||||
import { createPrefixedIdSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: createPrefixedIdSchema({ allowEmpty: true }).optional(),
|
||||
trackingId: z.string().default("").optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
|
||||
import { createPrefixedIdSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: createPrefixedIdSchema({ prefix: "G-", allowEmpty: true }).optional(),
|
||||
trackingId: z.string().default("").optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { createPrefixedIdSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: createPrefixedIdSchema({ prefix: "GTM-", addPrefixIfMissing: true, allowEmpty: false }),
|
||||
trackingId: z.string().transform((val) => {
|
||||
let trackingId = val.trim();
|
||||
// Ensure that trackingId is transformed if needed to begin with "GTM-" always
|
||||
trackingId = !trackingId.startsWith("GTM-") ? `GTM-${trackingId}` : trackingId;
|
||||
return trackingId;
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { alphanumericIdSchema, safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
SITE_ID: alphanumericIdSchema.optional(),
|
||||
SCRIPT_URL: safeUrlSchema.optional(),
|
||||
SITE_ID: z.string().optional(),
|
||||
SCRIPT_URL: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { numericIdSchema, safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
MATOMO_URL: safeUrlSchema.optional(),
|
||||
SITE_ID: numericIdSchema.optional(),
|
||||
MATOMO_URL: z.string().optional(),
|
||||
SITE_ID: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,18 +2,9 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
// Meta Pixel IDs are numeric strings of 15-16 digits
|
||||
const metaPixelIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => val === "" || /^[0-9]{15,16}$/.test(val), {
|
||||
message: "Invalid Meta Pixel ID format. Expected a numeric ID (e.g., 1234567890123456)",
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: metaPixelIdSchema,
|
||||
trackingId: z.string().default("").optional(),
|
||||
})
|
||||
);
|
||||
export const appKeysSchema = z.object({});
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
|
||||
import { safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
// Domain schema for Plausible tracking (e.g., example.com, sub.example.com)
|
||||
const domainSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim().toLowerCase())
|
||||
.refine(
|
||||
(val) =>
|
||||
val === "" || /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(val),
|
||||
{
|
||||
message: "Invalid domain format. Expected format: example.com",
|
||||
}
|
||||
)
|
||||
.optional();
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
PLAUSIBLE_URL: safeUrlSchema.optional().default("https://plausible.io/js/script.js").or(z.undefined()),
|
||||
trackingId: domainSchema,
|
||||
PLAUSIBLE_URL: z.string().optional().default("https://plausible.io/js/script.js").or(z.undefined()),
|
||||
trackingId: z.string().default("").optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,21 +2,10 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
// PostHog Project API Keys (typically start with phc_) - allow alphanumeric to not break legacy data
|
||||
const posthogIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[A-Za-z0-9_]+$/.test(val), {
|
||||
message: "Invalid PostHog Project API Key format. Expected alphanumeric characters or underscores",
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
TRACKING_ID: posthogIdSchema,
|
||||
API_HOST: safeUrlSchema.optional(),
|
||||
TRACKING_ID: z.string().optional(),
|
||||
API_HOST: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,18 +2,9 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
// Twipla Site IDs can be UUID or alphanumeric strings
|
||||
const twiplaSiteIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[A-Za-z0-9-]+$/.test(val), {
|
||||
message: "Invalid Twipla Site ID format. Expected alphanumeric characters or UUID",
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
SITE_ID: twiplaSiteIdSchema,
|
||||
SITE_ID: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,27 +2,10 @@ import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
// Umami Website IDs: UUID in v2 (e.g., 4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b) or numeric in v1
|
||||
const umamiSiteIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim().toLowerCase())
|
||||
.refine(
|
||||
(val) =>
|
||||
!val ||
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(val) ||
|
||||
/^[0-9]+$/.test(val),
|
||||
{
|
||||
message: "Invalid Umami Website ID format. Expected UUID or numeric ID",
|
||||
}
|
||||
)
|
||||
.optional();
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
SITE_ID: umamiSiteIdSchema,
|
||||
SCRIPT_URL: safeUrlSchema.default("https://cloud.umami.is/script.js").or(z.undefined()),
|
||||
SITE_ID: z.string().optional(),
|
||||
SCRIPT_URL: z.string().optional().default("https://cloud.umami.is/script.js").or(z.undefined()),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user