diff --git a/.env.example b/.env.example index 08c44c5c3b46b1..0a910e8429e44d 100644 --- a/.env.example +++ b/.env.example @@ -15,10 +15,10 @@ # - You can not repackage or sell the codebase # - Acquire a commercial license to remove these terms by visiting: cal.com/sales # -# To enable enterprise-only features, as an admin, go to /auth/setup to select your license and follow -# instructions. This environment variable is deprecated although still supported for backward compatibility. -# @see https://console.cal.com + +# To enable enterprise-only features please add your envrionment variable to the .env file then make your way to /auth/setup to select your license and follow instructions. CALCOM_LICENSE_KEY= +CALCOM_PRIVATE_API_ROUTE="https://goblin.cal.com" # *********************************************************************************************************** # - DATABASE ************************************************************************************************ diff --git a/apps/api/v1/lib/helpers/verifyApiKey.ts b/apps/api/v1/lib/helpers/verifyApiKey.ts index 23cbe74d433cb4..fca0077ebb3764 100644 --- a/apps/api/v1/lib/helpers/verifyApiKey.ts +++ b/apps/api/v1/lib/helpers/verifyApiKey.ts @@ -1,7 +1,7 @@ import type { NextMiddleware } from "next-api-middleware"; +import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService"; import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys"; -import checkLicense from "@calcom/features/ee/common/server/checkLicense"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; @@ -18,9 +18,12 @@ export const dateNotInPast = function (date: Date) { // This verifies the apiKey and sets the user if it is valid. export const verifyApiKey: NextMiddleware = async (req, res, next) => { - const hasValidLicense = await checkLicense(prisma); - if (!hasValidLicense && IS_PRODUCTION) + const licenseKeyService = await LicenseKeyService.create(); + const hasValidLicense = await licenseKeyService.checkLicense(); + + if (!hasValidLicense && IS_PRODUCTION) { return res.status(401).json({ error: "Invalid or missing CALCOM_LICENSE_KEY environment variable" }); + } // Check if the apiKey query param is provided. if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" }); // remove the prefix from the user provided api_key. If no env set default to "cal_" diff --git a/apps/api/v1/test/lib/middleware/verifyApiKey.test.ts b/apps/api/v1/test/lib/middleware/verifyApiKey.test.ts index 1b53565fd7ba70..a7214968edba93 100644 --- a/apps/api/v1/test/lib/middleware/verifyApiKey.test.ts +++ b/apps/api/v1/test/lib/middleware/verifyApiKey.test.ts @@ -3,9 +3,9 @@ import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock"; import type { Request, Response } from "express"; import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; -import { describe, vi, it, expect, afterEach } from "vitest"; +import { describe, vi, it, expect, afterEach, beforeEach } from "vitest"; -import checkLicense from "@calcom/features/ee/common/server/checkLicense"; +import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService"; import prisma from "@calcom/prisma"; import { isAdminGuard } from "~/lib/utils/isAdmin"; @@ -20,20 +20,21 @@ afterEach(() => { vi.resetAllMocks(); }); -vi.mock("@calcom/features/ee/common/server/checkLicense", () => { - return { - default: vi.fn(), - }; -}); - -vi.mock("~/lib/utils/isAdmin", () => { - return { - isAdminGuard: vi.fn(), - }; -}); +vi.mock("@calcom/prisma"); +vi.mock("~/lib/utils/isAdmin", () => ({ + isAdminGuard: vi.fn(), +})); describe("Verify API key", () => { - it("It should throw an error if the api key is not valid", async () => { + let service: LicenseKeyService; + + beforeEach(async () => { + service = await LicenseKeyService.create(); + + vi.spyOn(service, "checkLicense"); + }); + + it("should throw an error if the api key is not valid", async () => { const { req, res } = createMocks({ method: "POST", body: {}, @@ -43,7 +44,7 @@ describe("Verify API key", () => { fn: verifyApiKey, }; - vi.mocked(checkLicense).mockResolvedValue(false); + vi.mocked(service.checkLicense).mockResolvedValue(false); vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: false, scope: null }); const serverNext = vi.fn((next: void) => Promise.resolve(next)); @@ -53,9 +54,11 @@ describe("Verify API key", () => { await middleware.fn(req, res, serverNext); expect(middlewareSpy).toBeCalled(); + expect(res.statusCode).toBe(401); }); - it("It should throw an error if no api key is provided", async () => { + + it("should throw an error if no api key is provided", async () => { const { req, res } = createMocks({ method: "POST", body: {}, @@ -65,7 +68,7 @@ describe("Verify API key", () => { fn: verifyApiKey, }; - vi.mocked(checkLicense).mockResolvedValue(true); + vi.mocked(service.checkLicense).mockResolvedValue(true); vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: false, scope: null }); const serverNext = vi.fn((next: void) => Promise.resolve(next)); @@ -75,10 +78,11 @@ describe("Verify API key", () => { await middleware.fn(req, res, serverNext); expect(middlewareSpy).toBeCalled(); + expect(res.statusCode).toBe(401); }); - it("It should set correct permissions for system-wide admin", async () => { + it("should set correct permissions for system-wide admin", async () => { const { req, res } = createMocks({ method: "POST", body: {}, @@ -97,7 +101,7 @@ describe("Verify API key", () => { fn: verifyApiKey, }; - vi.mocked(checkLicense).mockResolvedValue(true); + vi.mocked(service.checkLicense).mockResolvedValue(true); vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: true, scope: ScopeOfAdmin.SystemWide }); const serverNext = vi.fn((next: void) => Promise.resolve(next)); @@ -107,11 +111,12 @@ describe("Verify API key", () => { await middleware.fn(req, res, serverNext); expect(middlewareSpy).toBeCalled(); + expect(req.isSystemWideAdmin).toBe(true); expect(req.isOrganizationOwnerOrAdmin).toBe(false); }); - it("It should set correct permissions for org-level admin", async () => { + it("should set correct permissions for org-level admin", async () => { const { req, res } = createMocks({ method: "POST", body: {}, @@ -130,7 +135,7 @@ describe("Verify API key", () => { fn: verifyApiKey, }; - vi.mocked(checkLicense).mockResolvedValue(true); + vi.mocked(service.checkLicense).mockResolvedValue(true); vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: true, scope: ScopeOfAdmin.OrgOwnerOrAdmin }); const serverNext = vi.fn((next: void) => Promise.resolve(next)); @@ -140,6 +145,7 @@ describe("Verify API key", () => { await middleware.fn(req, res, serverNext); expect(middlewareSpy).toBeCalled(); + expect(req.isSystemWideAdmin).toBe(false); expect(req.isOrganizationOwnerOrAdmin).toBe(true); }); diff --git a/apps/web/pages/settings/license-key/new/index.tsx b/apps/web/pages/settings/license-key/new/index.tsx index f217036ee22d4b..c51edfde8edb9e 100644 --- a/apps/web/pages/settings/license-key/new/index.tsx +++ b/apps/web/pages/settings/license-key/new/index.tsx @@ -1,6 +1,5 @@ "use client"; -import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; import { CreateANewLicenseKeyForm } from "@calcom/features/ee/deployment/licensekey/CreateLicenseKeyForm"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { WizardLayout, Meta } from "@calcom/ui"; @@ -12,10 +11,10 @@ import PageWrapper from "@components/PageWrapper"; const CreateNewLicenseKeyPage = () => { const { t } = useLocale(); return ( - + <> - + ); }; const LayoutWrapper = (page: React.ReactElement) => { @@ -30,4 +29,5 @@ CreateNewLicenseKeyPage.getLayout = LayoutWrapper; CreateNewLicenseKeyPage.PageWrapper = PageWrapper; export default CreateNewLicenseKeyPage; + export { getServerSideProps }; diff --git a/packages/features/auth/lib/getServerSession.ts b/packages/features/auth/lib/getServerSession.ts index 734debd0e7bee4..f87cfd83cbd722 100644 --- a/packages/features/auth/lib/getServerSession.ts +++ b/packages/features/auth/lib/getServerSession.ts @@ -3,7 +3,7 @@ import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from import type { AuthOptions, Session } from "next-auth"; import { getToken } from "next-auth/jwt"; -import checkLicense from "@calcom/features/ee/common/server/checkLicense"; +import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService"; import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -65,7 +65,8 @@ export async function getServerSession(options: { return null; } - const hasValidLicense = await checkLicense(prisma); + const licenseKeyService = await LicenseKeyService.create(); + const hasValidLicense = await licenseKeyService.checkLicense(); let upId = token.upId; diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index f5a12f2cb9c289..a16f84d804ac0e 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -7,7 +7,7 @@ import CredentialsProvider from "next-auth/providers/credentials"; import EmailProvider from "next-auth/providers/email"; import GoogleProvider from "next-auth/providers/google"; -import checkLicense from "@calcom/features/ee/common/server/checkLicense"; +import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService"; import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg"; import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider"; import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; @@ -619,7 +619,9 @@ export const AUTH_OPTIONS: AuthOptions = { }, async session({ session, token, user }) { log.debug("callbacks:session - Session callback called", safeStringify({ session, token, user })); - const hasValidLicense = await checkLicense(prisma); + const licenseKeyService = await LicenseKeyService.create(); + const hasValidLicense = await licenseKeyService.checkLicense(); + const profileId = token.profileId; const calendsoSession: Session = { ...session, diff --git a/packages/features/ee/common/server/LicenseKeyService.test.ts b/packages/features/ee/common/server/LicenseKeyService.test.ts new file mode 100644 index 00000000000000..d56a6dccf8331e --- /dev/null +++ b/packages/features/ee/common/server/LicenseKeyService.test.ts @@ -0,0 +1,190 @@ +import "../../../../../tests/libs/__mocks__/prisma"; + +import * as cache from "memory-cache"; +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; + +import { getDeploymentKey } from "../../deployment/lib/getDeploymentKey"; +import { createSignature, generateNonce } from "./private-api-utils"; + +const baseUrl = "http://test-api.com"; +const licenseKey = "test-license-key"; + +/** So we can override in specific test below */ +process.env.NEXT_PUBLIC_IS_E2E = "0"; +/** All fetch call in LicenseKeyService will fail without this */ +process.env.CAL_SIGNATURE_TOKEN = "dummy"; + +/** + * This is needed to ensure the constants and env are fresh. + * If not, we would need to override constants on each scenario with differing constants/env vars. + */ +async function getLicenseKeyService() { + return (await import("./LicenseKeyService")).default; +} + +async function stubEnvAndReload(key: string, value: string) { + // We set env variable + vi.stubEnv(key, value); + // We refresh constants and prevent cached modules. + // @see https://github.com/vitest-dev/vitest/issues/4232#issuecomment-1745452522 + vi.resetModules(); + await import("@calcom/lib/constants"); +} + +// Mock dependencies +vi.mock("memory-cache", () => ({ + get: vi.fn(), + put: vi.fn(), +})); + +vi.mock("../../deployment/lib/getDeploymentKey", () => ({ + getDeploymentKey: vi.fn(), +})); + +vi.mock("./private-api-utils", () => ({ + generateNonce: vi.fn(), + createSignature: vi.fn(), +})); + +const BASE_HEADERS = { + "Content-Type": "application/json", +}; + +describe("LicenseKeyService", () => { + beforeEach(async () => { + vi.mocked(getDeploymentKey).mockResolvedValue(licenseKey); + }); + + afterEach(() => { + vi.resetAllMocks(); + vi.unstubAllEnvs(); + }); + + describe("create", () => { + it("should create an instance of LicenseKeyService", async () => { + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + expect(service).toBeInstanceOf(LicenseKeyService); + }); + }); + + describe("incrementUsage", () => { + it("should call the incrementUsage API and return the response", async () => { + const mockResponse = { success: true }; + const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValue({ + json: vi.fn().mockResolvedValue(mockResponse), + } as any); + + vi.mocked(generateNonce).mockReturnValue("mocked-nonce"); + vi.mocked(createSignature).mockReturnValue("mocked-signature"); + stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl); + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + const response = await service.incrementUsage(); + expect(response).toEqual(mockResponse); + expect(fetchSpy).toHaveBeenCalledWith(`${baseUrl}/v1/license/usage/increment?event=booking`, { + body: undefined, + headers: { + ...BASE_HEADERS, + nonce: "mocked-nonce", + signature: "mocked-signature", + "x-cal-license-key": "test-license-key", + }, + method: "POST", + mode: "cors", + }); + }); + + it("should throw an error if the API call fails", async () => { + const fetchSpy = vi.spyOn(global, "fetch").mockRejectedValue(new Error("API Failure")); + vi.mocked(generateNonce).mockReturnValue("mocked-nonce"); + vi.mocked(createSignature).mockReturnValue("mocked-signature"); + stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl); + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + await expect(service.incrementUsage()).rejects.toThrow("API Failure"); + expect(fetchSpy).toHaveBeenCalledWith(`${baseUrl}/v1/license/usage/increment?event=booking`, { + body: undefined, + headers: { + ...BASE_HEADERS, + nonce: "mocked-nonce", + signature: "mocked-signature", + "x-cal-license-key": "test-license-key", + }, + method: "POST", + mode: "cors", + }); + }); + }); + + describe("checkLicense", () => { + it("should return true if NEXT_PUBLIC_IS_E2E is set", async () => { + stubEnvAndReload("NEXT_PUBLIC_IS_E2E", "1"); + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + const result = await service.checkLicense(); + expect(result).toBe(true); + }); + + it("should return cached response if available", async () => { + const url = `${baseUrl}/v1/license/${licenseKey}`; + vi.mocked(cache.get).mockReturnValue(true); + stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl); + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + const result = await service.checkLicense(); + expect(result).toBe(true); + expect(cache.get).toHaveBeenCalledWith(url); + }); + + it("should fetch license validity from API if not cached", async () => { + const url = `${baseUrl}/v1/license/${licenseKey}`; + vi.mocked(cache.get).mockReturnValue(null); + const mockResponse = { status: true }; + const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValue({ + json: vi.fn().mockResolvedValue(mockResponse), + } as any); + vi.mocked(generateNonce).mockReturnValue("mocked-nonce"); + vi.mocked(createSignature).mockReturnValue("mocked-signature"); + stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl); + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + const result = await service.checkLicense(); + expect(result).toBe(true); + expect(cache.get).toHaveBeenCalledWith(url); + expect(fetchSpy).toHaveBeenCalledWith(url, { + body: undefined, + headers: { + ...BASE_HEADERS, + nonce: "mocked-nonce", + signature: "mocked-signature", + "x-cal-license-key": "test-license-key", + }, + mode: "cors", + }); + }); + + it("should return false if API call fails", async () => { + const url = `${baseUrl}/v1/license/${licenseKey}`; + stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl); + vi.mocked(cache.get).mockReturnValue(null); + vi.mocked(generateNonce).mockReturnValue("mocked-nonce"); + vi.mocked(createSignature).mockReturnValue("mocked-signature"); + const fetchSpy = vi.spyOn(global, "fetch").mockRejectedValue(new Error("API Failure")); + const LicenseKeyService = await getLicenseKeyService(); + const service = await LicenseKeyService.create(); + const result = await service.checkLicense(); + expect(result).toBe(false); + expect(fetchSpy).toHaveBeenCalledWith(url, { + body: undefined, + headers: { + ...BASE_HEADERS, + nonce: "mocked-nonce", + signature: "mocked-signature", + "x-cal-license-key": "test-license-key", + }, + mode: "cors", + }); + }); + }); +}); diff --git a/packages/features/ee/common/server/LicenseKeyService.ts b/packages/features/ee/common/server/LicenseKeyService.ts new file mode 100644 index 00000000000000..596805a692c5e5 --- /dev/null +++ b/packages/features/ee/common/server/LicenseKeyService.ts @@ -0,0 +1,97 @@ +import * as cache from "memory-cache"; + +import { CALCOM_PRIVATE_API_ROUTE } from "@calcom/lib/constants"; +import prisma from "@calcom/prisma"; + +import { getDeploymentKey } from "../../deployment/lib/getDeploymentKey"; +import { generateNonce, createSignature } from "./private-api-utils"; + +export enum UsageEvent { + BOOKING = "booking", + USER = "user", +} + +class LicenseKeyService { + private readonly baseUrl = CALCOM_PRIVATE_API_ROUTE; + private readonly licenseKey: string; + public readonly CACHING_TIME = 86_400_000; // 24 hours in milliseconds + + // Private constructor to prevent direct instantiation + private constructor(licenseKey: string) { + this.baseUrl = CALCOM_PRIVATE_API_ROUTE; + this.licenseKey = licenseKey; + } + + // Static async factory method + public static async create(): Promise { + const licenseKey = await getDeploymentKey(prisma); + return new LicenseKeyService(licenseKey); + } + + private async fetcher({ + url, + body, + options = {}, + }: { + url: string; + body?: Record; + options?: RequestInit; + }): Promise { + const nonce = generateNonce(); + const signatureToken = process.env.CAL_SIGNATURE_TOKEN; + if (!signatureToken) { + throw new Error("CAL_SIGNATURE_TOKEN needs to be set"); + } + const signature = createSignature(body || {}, nonce, signatureToken); + + const headers = { + ...options.headers, + "Content-Type": "application/json", + nonce: nonce, + signature: signature, + "x-cal-license-key": this.licenseKey, + }; + + return await fetch(url, { + ...options, + headers: headers, + body: JSON.stringify(body), + }); + } + + async incrementUsage(usageEvent?: UsageEvent) { + try { + const response = await this.fetcher({ + url: `${this.baseUrl}/v1/license/usage/increment?event=${usageEvent ?? UsageEvent.BOOKING}`, + options: { + method: "POST", + mode: "cors", + }, + }); + return await response.json(); + } catch (error) { + console.error("Incrementing usage failed:", error); + throw error; + } + } + + async checkLicense(): Promise { + /** We skip for E2E testing */ + if (process.env.NEXT_PUBLIC_IS_E2E === "1") return true; + /** We check first on env */ + const url = `${this.baseUrl}/v1/license/${this.licenseKey}`; + const cachedResponse = cache.get(url); + if (cachedResponse) return cachedResponse; + try { + const response = await this.fetcher({ url, options: { mode: "cors" } }); + const data = await response.json(); + cache.put(url, data.stauts, this.CACHING_TIME); + return data.status; + } catch (error) { + console.error("Check license failed:", error); + return false; + } + } +} + +export default LicenseKeyService; diff --git a/packages/features/ee/common/server/private-api-utils.ts b/packages/features/ee/common/server/private-api-utils.ts new file mode 100644 index 00000000000000..3c0856a1d42268 --- /dev/null +++ b/packages/features/ee/common/server/private-api-utils.ts @@ -0,0 +1,13 @@ +import crypto from "crypto"; + +export const generateNonce = (): string => { + return crypto.randomBytes(16).toString("hex"); +}; + +// Utility function to create a signature +export const createSignature = (body: Record, nonce: string, secretKey: string): string => { + return crypto + .createHmac("sha256", secretKey) + .update(JSON.stringify(body) + nonce) + .digest("hex"); +}; diff --git a/packages/features/ee/deployment/lib/getDeploymentKey.ts b/packages/features/ee/deployment/lib/getDeploymentKey.ts index 9210ba06311cb5..7b84552b1666ce 100644 --- a/packages/features/ee/deployment/lib/getDeploymentKey.ts +++ b/packages/features/ee/deployment/lib/getDeploymentKey.ts @@ -1,9 +1,13 @@ import type { PrismaClient } from "@calcom/prisma"; export async function getDeploymentKey(prisma: PrismaClient) { + if (process.env.CALCOM_LICENSE_KEY) { + return process.env.CALCOM_LICENSE_KEY; + } const deployment = await prisma.deployment.findUnique({ where: { id: 1 }, select: { licenseKey: true }, }); - return deployment?.licenseKey || process.env.CALCOM_LICENSE_KEY || ""; + + return deployment?.licenseKey || ""; } diff --git a/packages/features/ee/deployment/licensekey/CreateLicenseKeyForm.tsx b/packages/features/ee/deployment/licensekey/CreateLicenseKeyForm.tsx index 18cd5e03305bb7..fb0edd2e3173c1 100644 --- a/packages/features/ee/deployment/licensekey/CreateLicenseKeyForm.tsx +++ b/packages/features/ee/deployment/licensekey/CreateLicenseKeyForm.tsx @@ -72,7 +72,7 @@ const CreateANewLicenseKeyFormChild = ({ session }: { session: Ensure new PrismaClientWithoutExtension({ ...prismaOptions, ...options }) + .$extends(usageTrackingExtention()) .$extends(excludePendingPaymentsExtension()) .$extends(bookingIdempotencyKeyExtension()) .$extends(withAccelerate()); @@ -32,6 +34,7 @@ bookingReferenceMiddleware(prismaWithoutClientExtensions); // FIXME: Due to some reason, there are types failing in certain places due to the $extends. Fix it and then enable it // Specifically we get errors like `Type 'string | Date | null | undefined' is not assignable to type 'Exact'` const prismaWithClientExtensions = prismaWithoutClientExtensions + .$extends(usageTrackingExtention()) .$extends(excludePendingPaymentsExtension()) .$extends(bookingIdempotencyKeyExtension()) .$extends(withAccelerate()); diff --git a/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.handler.ts b/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.handler.ts index f69d7d388c3ff4..b499ae3d557706 100644 --- a/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.handler.ts @@ -1,6 +1,8 @@ import * as crypto from "crypto"; import { z } from "zod"; +import { CALCOM_PRIVATE_API_ROUTE } from "@calcom/lib/constants"; + import type { TrpcSessionUser } from "../../../trpc"; import type { TCreateSelfHostedLicenseSchema } from "./createSelfHostedLicenseKey.schema"; @@ -49,7 +51,7 @@ const fetchWithSignature = async ( }; const createSelfHostedInstance = async ({ input, ctx }: GetOptions) => { - const privateApiUrl = process.env.CALCOM_PRIVATE_API_ROUTE; + const privateApiUrl = CALCOM_PRIVATE_API_ROUTE; const signatureToken = process.env.CAL_SIGNATURE_TOKEN; if (!privateApiUrl || !signatureToken) { diff --git a/yarn.lock b/yarn.lock index b02ce66b97746f..2bb45cf3db203a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4216,7 +4216,7 @@ __metadata: languageName: unknown linkType: soft -"@calcom/atoms@*, @calcom/atoms@workspace:packages/platform/atoms": +"@calcom/atoms@*, @calcom/atoms@workspace:^, @calcom/atoms@workspace:packages/platform/atoms": version: 0.0.0-use.local resolution: "@calcom/atoms@workspace:packages/platform/atoms" dependencies: @@ -5249,6 +5249,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/skype@workspace:packages/app-store/skype": + version: 0.0.0-use.local + resolution: "@calcom/skype@workspace:packages/app-store/skype" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/storybook@workspace:apps/storybook": version: 0.0.0-use.local resolution: "@calcom/storybook@workspace:apps/storybook" @@ -5664,6 +5673,7 @@ __metadata: dependencies: "@algora/sdk": ^0.1.2 "@calcom/app-store": "*" + "@calcom/atoms": "workspace:^" "@calcom/config": "*" "@calcom/dayjs": "*" "@calcom/embed-react": "workspace:^" @@ -5709,6 +5719,7 @@ __metadata: "@vercel/og": ^0.5.0 autoprefixer: ^10.4.12 bcryptjs: ^2.4.3 + class-variance-authority: ^0.7.0 clsx: ^1.2.1 cobe: ^0.4.1 concurrently: ^7.6.0 @@ -5721,6 +5732,7 @@ __metadata: env-cmd: ^10.1.0 eslint: ^8.34.0 fathom-client: ^3.5.0 + framer-motion: ^11.0.25 globby: ^13.1.3 graphql: ^16.8.0 graphql-codegen: ^0.4.0 @@ -5730,7 +5742,7 @@ __metadata: i18n-unused: ^0.13.0 iframe-resizer-react: ^1.1.0 keen-slider: ^6.8.0 - lucide-react: ^0.171.0 + lucide-react: ^0.364.0 micro: ^10.0.1 next: ^14.1.3 next-auth: ^4.22.1 @@ -5742,7 +5754,7 @@ __metadata: prism-react-renderer: ^1.3.5 react: ^18.2.0 react-confetti: ^6.0.1 - react-datocms: ^3.1.0 + react-datocms: ^5.0.3 react-device-detect: ^2.2.2 react-dom: ^18.2.0 react-fast-marquee: ^1.6.4 @@ -5752,8 +5764,10 @@ __metadata: react-live-chat-loader: ^2.8.1 react-markdown: ^9.0.1 react-merge-refs: 1.1.0 + react-parallax-tilt: ^1.7.226 react-resize-detector: ^9.1.0 react-twemoji: ^0.3.0 + react-twitter-embed: ^4.0.4 react-use-measure: ^2.1.1 react-wrap-balancer: ^1.0.0 remark: ^14.0.2 @@ -9507,6 +9521,59 @@ __metadata: languageName: node linkType: hard +"@mux/mux-player-react@npm:*": + version: 2.8.0 + resolution: "@mux/mux-player-react@npm:2.8.0" + dependencies: + "@mux/mux-player": 2.8.0 + "@mux/playback-core": 0.25.0 + prop-types: ^15.7.2 + peerDependencies: + "@types/react": ^17.0.0 || ^18 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: b4891a151ff92bbee3b9d2e0411c21be07b976c30f6e13c30b30cda97eeb5d4a88bb3d78fd58dac447b05bb12563234a9a5fbba3bdf79e0c274575cf9bb020cd + languageName: node + linkType: hard + +"@mux/mux-player@npm:2.8.0": + version: 2.8.0 + resolution: "@mux/mux-player@npm:2.8.0" + dependencies: + "@mux/mux-video": 0.20.0 + "@mux/playback-core": 0.25.0 + media-chrome: ~3.2.3 + checksum: 6a8e410456a325c2e59ef3ebb4f3efba69596a4df3e38e9ccb332af23fe16c571a8271c51e97ea5f003352f09f63fdb311ff548c0b2f613b7ca4c47fa68fcade + languageName: node + linkType: hard + +"@mux/mux-video@npm:0.20.0": + version: 0.20.0 + resolution: "@mux/mux-video@npm:0.20.0" + dependencies: + "@mux/playback-core": 0.25.0 + castable-video: ~1.0.9 + custom-media-element: ~1.3.1 + media-tracks: ~0.3.2 + checksum: 2f316a6697ff63dbcc5428216600ced1db14e22c195567c69e826b29b2752b9aeee785f073bc2eef71d7422b696370608d9d081b8e24b5332dfeb57bf0839791 + languageName: node + linkType: hard + +"@mux/playback-core@npm:0.25.0": + version: 0.25.0 + resolution: "@mux/playback-core@npm:0.25.0" + dependencies: + hls.js: ~1.5.11 + mux-embed: ~5.2.0 + checksum: 37175985f7ec7df0b2f24e9b6e3df9c39f94da81b0f1e59b989dd9d04d9451dae2ea6e06d4208a673e532ea2351c39c2d2ec0e149cde60a9a09b9dbd00ea2866 + languageName: node + linkType: hard + "@ndelangen/get-tarball@npm:^3.0.7": version: 3.0.9 resolution: "@ndelangen/get-tarball@npm:3.0.9" @@ -21502,6 +21569,15 @@ __metadata: languageName: node linkType: hard +"castable-video@npm:~1.0.9": + version: 1.0.10 + resolution: "castable-video@npm:1.0.10" + dependencies: + custom-media-element: ~1.3.2 + checksum: 5b7a27aacf305f40c6867e96e773f4290260f6eb8b01fe1f209506f8f6dec4cd099f3e35ee57de098829e4bcc688ccdc2cbb2fc86c509712dd573eadf66d42cc + languageName: node + linkType: hard + "ccount@npm:^2.0.0": version: 2.0.1 resolution: "ccount@npm:2.0.1" @@ -21966,6 +22042,15 @@ __metadata: languageName: node linkType: hard +"class-variance-authority@npm:^0.7.0": + version: 0.7.0 + resolution: "class-variance-authority@npm:0.7.0" + dependencies: + clsx: 2.0.0 + checksum: e7fd1fab433ef06f52a1b7b241b70b4a185864deef199d3b0a2c3412f1cc179517288264c383f3b971a00d76811625fc8f7ffe709e6170219e88cd7368f08a20 + languageName: node + linkType: hard + "classnames@npm:^2.2.5, classnames@npm:^2.2.6": version: 2.3.2 resolution: "classnames@npm:2.3.2" @@ -22238,6 +22323,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:2.0.0": + version: 2.0.0 + resolution: "clsx@npm:2.0.0" + checksum: a2cfb2351b254611acf92faa0daf15220f4cd648bdf96ce369d729813b85336993871a4bf6978ddea2b81b5a130478339c20d9d0b5c6fc287e5147f0c059276e + languageName: node + linkType: hard + "clsx@npm:^1.1.1": version: 1.1.1 resolution: "clsx@npm:1.1.1" @@ -23543,6 +23635,13 @@ __metadata: languageName: node linkType: hard +"custom-media-element@npm:~1.3.1, custom-media-element@npm:~1.3.2": + version: 1.3.2 + resolution: "custom-media-element@npm:1.3.2" + checksum: 9ec2ff88c3c5ba7d20b9484c0359b4cfe08ad6804f3116934ffbae2f2e0e04d2a832beb6f0bec56027a969ab366727d217406f750890613c988de6de5330cb4f + languageName: node + linkType: hard + "d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": version: 3.2.3 resolution: "d3-array@npm:3.2.3" @@ -27590,6 +27689,26 @@ __metadata: languageName: node linkType: hard +"framer-motion@npm:^11.0.25": + version: 11.3.12 + resolution: "framer-motion@npm:11.3.12" + dependencies: + tslib: ^2.4.0 + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: d13fa5fd2a21e73846a1171b8e6cad0be214b48f0c9b91070397975977758a98c66ea55d3e8dc027d959831027d76d0d8044cf41c49942cc3362a7b0cce7d4a0 + languageName: node + linkType: hard + "fresh@npm:0.5.2, fresh@npm:^0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" @@ -29262,6 +29381,13 @@ __metadata: languageName: node linkType: hard +"hls.js@npm:~1.5.11": + version: 1.5.13 + resolution: "hls.js@npm:1.5.13" + checksum: 534b48b18638fefe7788fd31d92b0b4e3820271b470fbd18c29cd46d51e6381b6f011d732f6196d4cda3148c81d8cb7740e1d1d0f2da0031e808b468198142f3 + languageName: node + linkType: hard + "hmac-drbg@npm:^1.0.1": version: 1.0.1 resolution: "hmac-drbg@npm:1.0.1" @@ -34082,15 +34208,6 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.171.0": - version: 0.171.0 - resolution: "lucide-react@npm:0.171.0" - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 - checksum: 768ffe368c52a518ee339203d86ff4479989ab4d79c0716f721900c4bb7392ef6ff7a14807f6a685abd74d27f4c1778170bff77a0ab4c3e06c17944b557d8300 - languageName: node - linkType: hard - "lucide-react@npm:^0.364.0": version: 0.364.0 resolution: "lucide-react@npm:0.364.0" @@ -34660,6 +34777,20 @@ __metadata: languageName: node linkType: hard +"media-chrome@npm:~3.2.3": + version: 3.2.4 + resolution: "media-chrome@npm:3.2.4" + checksum: 6401f509f4075cd7d02f8a479e81bcb084cd06104aa17fd7ebe6623bd171eaa52e1e1333c1895ad84c1f794ea3889cca13db52dd49b64129d67dd8be558d6eb2 + languageName: node + linkType: hard + +"media-tracks@npm:~0.3.2": + version: 0.3.3 + resolution: "media-tracks@npm:0.3.3" + checksum: 4795af3f171d7ad3a68d1ac1c1e8166a735244fe57d3fc0ec53b1c7410799e524756fc0bfb389632aebb148436b03baade36ca34a3f5377776c3968f6d2cc580 + languageName: node + linkType: hard + "media-typer@npm:0.3.0": version: 0.3.0 resolution: "media-typer@npm:0.3.0" @@ -36037,6 +36168,13 @@ __metadata: languageName: node linkType: hard +"mux-embed@npm:~5.2.0": + version: 5.2.1 + resolution: "mux-embed@npm:5.2.1" + checksum: ec34d3e003e2520be6d1386ca2383daa198a1fa3a469ca3f06c11a742d6ab136eef4944c37e8f2dcaa0585e1b0a6dfc119e3cf2302c7eb0e7f2ebc7c1fc95fbd + languageName: node + linkType: hard + "mysql2@npm:3.9.1": version: 3.9.1 resolution: "mysql2@npm:3.9.1" @@ -40345,20 +40483,21 @@ __metadata: languageName: node linkType: hard -"react-datocms@npm:^3.1.0": - version: 3.1.4 - resolution: "react-datocms@npm:3.1.4" +"react-datocms@npm:^5.0.3": + version: 5.0.3 + resolution: "react-datocms@npm:5.0.3" dependencies: + "@mux/mux-player-react": "*" datocms-listen: ^0.1.9 datocms-structured-text-generic-html-renderer: ^2.0.1 datocms-structured-text-utils: ^2.0.1 - react-intersection-observer: ^8.33.1 + react-intersection-observer: ^9.4.3 react-string-replace: ^1.1.0 universal-base64: ^2.1.0 use-deep-compare-effect: ^1.6.1 peerDependencies: react: ">= 16.12.0" - checksum: 54aba12aef4937175c2011548a8a576c96c8d8a596e84d191826910624c1d596e76a49782689dc236388a10803b02e700ac820cb7500cca7fd147a81f6c544c3 + checksum: 22c20152afb54424acfe967a2c8c525cd9f132a33374f2aba0231f16ea64dade389b096e2dac8de9ffded612bc32e9891d725609ee947639fe1cef907cb143f5 languageName: node linkType: hard @@ -40610,12 +40749,16 @@ __metadata: languageName: node linkType: hard -"react-intersection-observer@npm:^8.33.1": - version: 8.34.0 - resolution: "react-intersection-observer@npm:8.34.0" +"react-intersection-observer@npm:^9.4.3": + version: 9.13.0 + resolution: "react-intersection-observer@npm:9.13.0" peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0 - checksum: 7713fecfd1512c7f5a60f9f0bf15403b8f8bbd4110bcafaeaea6de36a0e0eb60368c3638f99e9c97b75ad8fc787ea48c241dcb5c694f821d7f2976f709082cc5 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react-dom: + optional: true + checksum: ee2a163b078923c1556814834f83d7c3ea3e8a9d3ceef974351fd52afe12163518ec57cb2eb0f6544ac255ac4b64d2e7652bc5d60c255159c0f25bda57a565b6 languageName: node linkType: hard @@ -40725,6 +40868,16 @@ __metadata: languageName: node linkType: hard +"react-parallax-tilt@npm:^1.7.226": + version: 1.7.232 + resolution: "react-parallax-tilt@npm:1.7.232" + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 8c866c9c06315aeaf1eddd4e5265db706e010d788667c337be552cbf8786e8b39f19cafc529be6c69d925e25b31705c52398e90bd89a805bf8914bbb46a8ebdb + languageName: node + linkType: hard + "react-phone-input-2@npm:^2.15.1": version: 2.15.1 resolution: "react-phone-input-2@npm:2.15.1" @@ -41139,6 +41292,18 @@ __metadata: languageName: node linkType: hard +"react-twitter-embed@npm:^4.0.4": + version: 4.0.4 + resolution: "react-twitter-embed@npm:4.0.4" + dependencies: + scriptjs: ^2.5.9 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: cdb3c5bd04c4da0efa767476be47c0a3865fb6335f2a1b9e242170167b51615c38164223278cef60c77143c4bac27ba582cbea054d0af3f138104fa5ec537c4c + languageName: node + linkType: hard + "react-universal-interface@npm:^0.6.2": version: 0.6.2 resolution: "react-universal-interface@npm:0.6.2" @@ -42963,6 +43128,13 @@ __metadata: languageName: node linkType: hard +"scriptjs@npm:^2.5.9": + version: 2.5.9 + resolution: "scriptjs@npm:2.5.9" + checksum: fc84cb6b60b6fb9aa6f1b3bc59fc94b233bd5241ed3a04233579014382b5eb60640269c87d8657902acc09f9b785ee33230c218627cea00e653564bda8f5acb6 + languageName: node + linkType: hard + "scuid@npm:^1.1.0": version: 1.1.0 resolution: "scuid@npm:1.1.0"