diff --git a/apps/api/lib/helpers/rateLimitApiKey.ts b/apps/api/lib/helpers/rateLimitApiKey.ts new file mode 100644 index 0000000000..7f1469ea7f --- /dev/null +++ b/apps/api/lib/helpers/rateLimitApiKey.ts @@ -0,0 +1,15 @@ +import type { NextMiddleware } from "next-api-middleware"; + +import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; + +export const rateLimitApiKey: NextMiddleware = async (req, res, next) => { + if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" }); + + // TODO: Add a way to add trusted api keys + await checkRateLimitAndThrowError({ + identifier: req.query.apiKey as string, + rateLimitingType: "api", + }); + + await next(); +}; diff --git a/apps/api/lib/helpers/withMiddleware.ts b/apps/api/lib/helpers/withMiddleware.ts index 37a7b0aa3e..d8cc9d6a13 100644 --- a/apps/api/lib/helpers/withMiddleware.ts +++ b/apps/api/lib/helpers/withMiddleware.ts @@ -12,6 +12,7 @@ import { HTTP_GET_OR_POST, HTTP_GET_DELETE_PATCH, } from "./httpMethods"; +import { rateLimitApiKey } from "./rateLimitApiKey"; import { verifyApiKey } from "./verifyApiKey"; import { withPagination } from "./withPagination"; @@ -25,6 +26,7 @@ const withMiddleware = label( HTTP_DELETE, addRequestId, verifyApiKey, + rateLimitApiKey, customPrismaClient, extendRequest, pagination: withPagination, @@ -37,6 +39,7 @@ const withMiddleware = label( // - Put customPrismaClient before verifyApiKey always. "customPrismaClient", "verifyApiKey", + "rateLimitApiKey", "addRequestId", ] // <-- Provide a list of middleware to call automatically ); diff --git a/packages/lib/rateLimit.ts b/packages/lib/rateLimit.ts index 57bf9f3e80..0608d47eea 100644 --- a/packages/lib/rateLimit.ts +++ b/packages/lib/rateLimit.ts @@ -7,7 +7,7 @@ import logger from "./logger"; const log = logger.getChildLogger({ prefix: ["RateLimit"] }); export type RateLimitHelper = { - rateLimitingType?: "core" | "forcedSlowMode" | "common"; + rateLimitingType?: "core" | "forcedSlowMode" | "common" | "api"; identifier: string; }; @@ -69,6 +69,12 @@ export function rateLimiter() { prefix: "ratelimit:slowmode", limiter: Ratelimit.fixedWindow(1, "30s"), }), + api: new Ratelimit({ + redis, + analytics: true, + prefix: "ratelimit:api", + limiter: Ratelimit.fixedWindow(10, "60s"), + }), }; async function rateLimit({ rateLimitingType = "core", identifier }: RateLimitHelper) {