feat: Improve error handling in ratelimit (#25223)

* Improve error handling in ratelimit

* Update route.ts

* Update route.ts

* Update route.ts

* address feedback
This commit is contained in:
sean-brydon
2025-12-12 20:39:23 +00:00
committed by GitHub
parent 983af06787
commit 13103c0881
6 changed files with 49 additions and 2 deletions
+11 -2
View File
@@ -7,6 +7,9 @@ import { z } from "zod";
import { validPassword } from "@calcom/features/auth/lib/validPassword";
import { hashPassword } from "@calcom/lib/auth/hashPassword";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import getIP from "@calcom/lib/getIP";
import { piiHasher } from "@calcom/lib/server/PiiHasher";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
@@ -36,8 +39,14 @@ async function handler(req: NextRequest) {
// token verified, delete the cookie / a resubmit on failure requires a new csrf token.
cookieStore.delete("calcom.csrf_token");
// rate-limited there is a low, very low chance that a password request stays valid long enough
// to brute force 3.8126967e+40 options.
const remoteIp = getIP(req);
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: `api:reset-password:${piiHasher.hash(remoteIp)}`,
});
// Note: There is a low, very low chance that a password request stays valid long enough
// to brute force 3.8126967e+40 options, but rate limiting provides additional protection.
const maybeRequest = await prisma.resetPasswordRequest.findFirstOrThrow({
where: {
id: rawRequestId,
+8
View File
@@ -5,10 +5,12 @@ import { NextResponse, type NextRequest } from "next/server";
import calcomSignupHandler from "@calcom/feature-auth/signup/handlers/calcomHandler";
import selfHostedSignupHandler from "@calcom/feature-auth/signup/handlers/selfHostedHandler";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import getIP from "@calcom/lib/getIP";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { piiHasher } from "@calcom/lib/server/PiiHasher";
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
import { prisma } from "@calcom/prisma";
import { signupSchema } from "@calcom/prisma/zod-utils";
@@ -38,6 +40,12 @@ async function handler(req: NextRequest) {
const remoteIp = getIP(req);
// Use a try catch instead of returning res every time
try {
// Rate limit: 10 signups per 60 seconds per IP
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: `api:signup:${piiHasher.hash(remoteIp)}`,
});
const body = await parseRequestData(req);
await checkCfTurnstileToken({
token: req.headers.get("cf-access-token") as string,
@@ -7,6 +7,7 @@ import type { NextRequest } from "next/server";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
import prisma from "@calcom/prisma";
@@ -27,6 +28,11 @@ async function handler(req: NextRequest) {
return NextResponse.json({ error: ErrorCode.InternalServerError }, { status: 500 });
}
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: `api:totp-disable:${session.user.id}`,
});
const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { password: true } });
if (!user) {
@@ -6,6 +6,7 @@ import type { NextRequest } from "next/server";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
import prisma from "@calcom/prisma";
@@ -25,6 +26,11 @@ async function postHandler(req: NextRequest) {
return NextResponse.json({ error: ErrorCode.InternalServerError }, { status: 500 });
}
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: `api:totp-enable:${session.user.id}`,
});
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
if (!user) {
console.error(`Session references user that no longer exists.`);
@@ -10,6 +10,7 @@ import qrcode from "qrcode";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { symmetricEncrypt } from "@calcom/lib/crypto";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
@@ -29,6 +30,11 @@ async function postHandler(req: NextRequest) {
return NextResponse.json({ error: ErrorCode.InternalServerError }, { status: 500 });
}
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: `api:totp-setup:${session.user.id}`,
});
const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { password: true } });
if (!user) {
+12
View File
@@ -5,6 +5,9 @@ import type { NextRequest } from "next/server";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import getIP from "@calcom/lib/getIP";
import { piiHasher } from "@calcom/lib/server/PiiHasher";
import { bookingCancelWithCsrfSchema } from "@calcom/prisma/zod-utils";
import { validateCsrfToken } from "@calcom/web/lib/validateCsrfToken";
@@ -26,6 +29,15 @@ async function handler(req: NextRequest) {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
// Rate limit: 10 booking cancellations per 60 seconds per user (or IP if not authenticated)
const identifier = session?.user?.id
? `api:cancel-user:${session.user.id}`
: `api:cancel-ip:${piiHasher.hash(getIP(req))}`;
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier,
});
const result = await handleCancelBooking({
bookingData,
userId: session?.user?.id || -1,