fix: status code or router/embed rate limiting (#21968)

* fix: status code or router embed rate limiting

* fix: type err

* fix: handle api v2

* fix: status code

* chore: remove unused

* chore: update unit test

* chore: unit test

* refactor: make res required

* chore: remove unused

* chore: add a wrapper in getserversideprops

* chore: remlove log

* Add ratelLimiting unit test

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
This commit is contained in:
Udit TakkarandHariom authored and GitHub committed 2025-06-30 14:58:44 +05:30
1 parent d27490e6f2
commit 3b0bb25251
4 files changed
+40 -4

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ export default function Router({ form, message, errorMessage }: inferSSRProps<ty
return (
<>
<Head>
<title>{form.name} | Cal.com Forms</title>
<title>{form?.name} | Cal.com Forms</title>
</Head>
<div className="mx-auto my-0 max-w-3xl md:my-24">
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
@@ -1,11 +1,30 @@
import { wrapGetServerSidePropsWithSentry } from "@sentry/nextjs";
import type { GetServerSidePropsContext } from "next";
import { getRoutedUrl } from "@calcom/lib/server/getRoutedUrl";
import { getRoutedUrl, hasEmbedPath } from "@calcom/lib/server/getRoutedUrl";
import { TRPCError } from "@trpc/server";
export const getServerSideProps = wrapGetServerSidePropsWithSentry(async function getServerSideProps(
context: GetServerSidePropsContext
) {
return await getRoutedUrl(context);
try {
return await getRoutedUrl(context);
} catch (error) {
if (error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS") {
context.res.statusCode = 429;
const isEmbed = hasEmbedPath(context.req.url || "");
return {
props: {
isEmbed,
form: null,
message: null,
errorMessage: error.message,
},
};
}
throw error;
}
},
"/router");
+17
View File
@@ -1,5 +1,6 @@
import "@calcom/lib/__mocks__/logger";
import { createHash } from "crypto";
import type { GetServerSidePropsContext } from "next";
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -12,12 +13,14 @@ import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substit
import { getUrlSearchParamsToForward } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm";
import { UserRepository } from "@calcom/lib/server/repository/user";
import { getRoutedUrl } from "./getRoutedUrl";
// Mock dependencies
vi.mock("@calcom/lib/checkRateLimitAndThrowError");
vi.mock("@calcom/app-store/routing-forms/lib/handleResponse");
vi.mock("@calcom/lib/server/repository/routingForm");
vi.mock("@calcom/lib/server/repository/user");
@@ -112,6 +115,7 @@ describe("getRoutedUrl", () => {
it("should return notFound if form is not found", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(null);
const context = mockContext({});
const result = await getRoutedUrl(context);
expect(result).toEqual({ notFound: true });
expect(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).toHaveBeenCalledWith("form-id");
@@ -259,6 +263,19 @@ describe("getRoutedUrl", () => {
);
});
it("should throw an error if rate limit is exceeded", async () => {
vi.mocked(checkRateLimitAndThrowError).mockRejectedValue(new Error("Rate limit exceeded"));
const context = mockContext({ email: "test@cal.com" });
const expectedHash = createHash("sha256")
.update(JSON.stringify({ email: "test@cal.com" }))
.digest("hex");
await expect(getRoutedUrl(context)).rejects.toThrow("Rate limit exceeded");
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
identifier: `form:form-id:hash:${expectedHash}`,
});
});
describe("Dry Run", () => {
it("should call handleResponse with isPreview:true", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
+1 -1
View File
@@ -44,7 +44,7 @@ const getDeterministicHashForResponse = (fieldsResponses: Record<string, unknown
return hash;
};
function hasEmbedPath(pathWithQuery: string) {
export function hasEmbedPath(pathWithQuery: string) {
const onlyPath = pathWithQuery.split("?")[0];
return onlyPath.endsWith("/embed") || onlyPath.endsWith("/embed/");
}