feat: usage base billed - license key check - move to private api (#15222)
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
co-authored by
Omar López
parent
53c37a9ddf
commit
e16662e655
+3
-3
@@ -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 ************************************************************************************************
|
||||
|
||||
@@ -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_"
|
||||
|
||||
@@ -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<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
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<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
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<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
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<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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 (
|
||||
<LicenseRequired>
|
||||
<>
|
||||
<Meta title={t("set_up_your_organization")} description={t("organizations_description")} />
|
||||
<CreateANewLicenseKeyForm />
|
||||
</LicenseRequired>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const LayoutWrapper = (page: React.ReactElement) => {
|
||||
@@ -30,4 +29,5 @@ CreateNewLicenseKeyPage.getLayout = LayoutWrapper;
|
||||
CreateNewLicenseKeyPage.PageWrapper = PageWrapper;
|
||||
|
||||
export default CreateNewLicenseKeyPage;
|
||||
|
||||
export { getServerSideProps };
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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<LicenseKeyService> {
|
||||
const licenseKey = await getDeploymentKey(prisma);
|
||||
return new LicenseKeyService(licenseKey);
|
||||
}
|
||||
|
||||
private async fetcher({
|
||||
url,
|
||||
body,
|
||||
options = {},
|
||||
}: {
|
||||
url: string;
|
||||
body?: Record<string, unknown>;
|
||||
options?: RequestInit;
|
||||
}): Promise<Response> {
|
||||
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<boolean> {
|
||||
/** 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;
|
||||
@@ -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<string, unknown>, nonce: string, secretKey: string): string => {
|
||||
return crypto
|
||||
.createHmac("sha256", secretKey)
|
||||
.update(JSON.stringify(body) + nonce)
|
||||
.digest("hex");
|
||||
};
|
||||
@@ -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 || "";
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ const CreateANewLicenseKeyFormChild = ({ session }: { session: Ensure<SessionCon
|
||||
function calculateMonthlyPrice() {
|
||||
const occurrence = watchedBillingPeriod === "MONTHLY" ? 1 : 12;
|
||||
|
||||
const sum = watchedEntityCount * watchedEntityPrice * occurrence;
|
||||
const sum = watchedEntityCount * watchedEntityPrice;
|
||||
return `$ ${sum / 100} / ${occurrence} months`;
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ export const CLOUDFLARE_SITE_ID = process.env.NEXT_PUBLIC_CLOUDFLARE_SITEKEY;
|
||||
export const MINIMUM_NUMBER_OF_ORG_SEATS = 30;
|
||||
export const ORG_SELF_SERVE_ENABLED = process.env.NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED === "1";
|
||||
export const ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE = 0;
|
||||
|
||||
export const CALCOM_PRIVATE_API_ROUTE = process.env.CALCOM_PRIVATE_API_ROUTE || "https://goblin.cal.com";
|
||||
/**
|
||||
* The maximum number of days we should check for if we don't find all required bookable days
|
||||
* Counter start from current day and we would like to not go beyond 2 months(max days possible) from current day.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import LicenseKeyService, { UsageEvent } from "@calcom/ee/common/server/LicenseKeyService";
|
||||
|
||||
export function usageTrackingExtention() {
|
||||
return Prisma.defineExtension({
|
||||
query: {
|
||||
booking: {
|
||||
async create({ args, query }) {
|
||||
try {
|
||||
const licenseKeyService = await LicenseKeyService.create();
|
||||
await licenseKeyService.incrementUsage();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
return query(args);
|
||||
},
|
||||
},
|
||||
user: {
|
||||
async create({ args, query }) {
|
||||
try {
|
||||
const licenseKeyService = await LicenseKeyService.create();
|
||||
await licenseKeyService.incrementUsage(UsageEvent.USER);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
return query(args);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { withAccelerate } from "@prisma/extension-accelerate";
|
||||
|
||||
import { bookingIdempotencyKeyExtension } from "./extensions/booking-idempotency-key";
|
||||
import { excludePendingPaymentsExtension } from "./extensions/exclude-pending-payment-teams";
|
||||
import { usageTrackingExtention } from "./extensions/usage-tracking";
|
||||
import { bookingReferenceMiddleware } from "./middleware";
|
||||
|
||||
const prismaOptions: Prisma.PrismaClientOptions = {};
|
||||
@@ -21,6 +22,7 @@ const prismaWithoutClientExtensions =
|
||||
|
||||
export const customPrisma = (options?: Prisma.PrismaClientOptions) =>
|
||||
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<string | Date | null | undefined, string | Date | null | undefined>'`
|
||||
const prismaWithClientExtensions = prismaWithoutClientExtensions
|
||||
.$extends(usageTrackingExtention())
|
||||
.$extends(excludePendingPaymentsExtension())
|
||||
.$extends(bookingIdempotencyKeyExtension())
|
||||
.$extends(withAccelerate());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user