fix: add improved logging and decrease ttl (#19979)
* add improved logging and decrease ttl * fix test TTL time --------- Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
This commit is contained in:
co-authored by
Tushar Bhatt
parent
b644ec58af
commit
49cb86dfe9
@@ -22,12 +22,6 @@ vi.mock("@calcom/prisma", () => ({
|
||||
},
|
||||
},
|
||||
}));
|
||||
vi.mock("@sentry/nextjs", () => ({
|
||||
captureException: vi.fn(),
|
||||
captureMessage: vi.fn(),
|
||||
setUser: vi.fn(),
|
||||
setTag: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("autoLock", () => {
|
||||
const mockRedis = {
|
||||
@@ -45,13 +39,11 @@ describe("autoLock", () => {
|
||||
// Mock environment variables
|
||||
process.env.UPSTASH_REDIS_REST_TOKEN = "test-token";
|
||||
process.env.UPSTASH_REDIS_REST_URL = "test-url";
|
||||
process.env.NEXT_PUBLIC_SENTRY_DSN = "sentry-dsn";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.UPSTASH_REDIS_REST_TOKEN;
|
||||
delete process.env.UPSTASH_REDIS_REST_URL;
|
||||
delete process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
});
|
||||
|
||||
describe("handleAutoLock", () => {
|
||||
@@ -110,7 +102,7 @@ describe("autoLock", () => {
|
||||
});
|
||||
|
||||
expect(mockRedis.set).toHaveBeenCalledWith("autolock:email:test@example.com.count", "3");
|
||||
expect(mockRedis.expire).toHaveBeenCalledWith("autolock:email:test@example.com.count", 3600);
|
||||
expect(mockRedis.expire).toHaveBeenCalledWith("autolock:email:test@example.com.count", 1800);
|
||||
expect(prisma.user.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -262,7 +254,7 @@ describe("autoLock", () => {
|
||||
});
|
||||
|
||||
expect(mockRedis.set).toHaveBeenCalledWith("autolock:email:test@example.com.count", "1");
|
||||
expect(mockRedis.expire).toHaveBeenCalledWith("autolock:email:test@example.com.count", 3600);
|
||||
expect(mockRedis.expire).toHaveBeenCalledWith("autolock:email:test@example.com.count", 1800);
|
||||
});
|
||||
|
||||
it("should handle Redis errors gracefully", async () => {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import type { RatelimitResponse } from "@unkey/ratelimit";
|
||||
|
||||
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
|
||||
import { RedisService } from "@calcom/features/redis/RedisService";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import logger from "./logger";
|
||||
|
||||
// This is the number of times a user can exceed the rate limit before being locked
|
||||
const DEFAULT_AUTOLOCK_THRESHOLD = 5;
|
||||
const DEFAULT_CHECK_THRESHOLD_WINDOW_DURATION = 60 * 60 * 1000; // 1 hour in milliseconds
|
||||
// This is the duration of the rate limit check window
|
||||
const DEFAULT_CHECK_THRESHOLD_WINDOW_DURATION = 60 * 30 * 1000; // 30 minutes in milliseconds
|
||||
|
||||
interface HandleAutoLockInput {
|
||||
identifier: string;
|
||||
@@ -17,6 +20,13 @@ interface HandleAutoLockInput {
|
||||
autolockDuration?: number; // in milliseconds
|
||||
}
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[autoLock]"] });
|
||||
|
||||
/**
|
||||
* The "Requests to Hit Limit × Threshold" shows how many requests would be needed to trigger an auto-lock if a user consistently hits
|
||||
* its their rate limit. For example, in the "core" * namespace, a user would need to make at least 50 requests (10 limit × 5 threshold)
|
||||
* within the 30-minute window to get auto-locked.
|
||||
*/
|
||||
export async function handleAutoLock({
|
||||
identifier: _identifier,
|
||||
identifierType,
|
||||
@@ -30,7 +40,7 @@ export async function handleAutoLock({
|
||||
const UPSTASH_ENV_FOUND = process.env.UPSTASH_REDIS_REST_TOKEN && process.env.UPSTASH_REDIS_REST_URL;
|
||||
|
||||
if (!UPSTASH_ENV_FOUND) {
|
||||
console.log("Skipping auto lock because UPSTASH env variables are not set");
|
||||
log.warn("Skipping auto lock because UPSTASH env variables are not set");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -48,8 +58,17 @@ export async function handleAutoLock({
|
||||
const count = await redis.get(lockKey);
|
||||
const currentCount = count ? parseInt(count.toString(), 10) : 0;
|
||||
|
||||
log.info(
|
||||
`Rate limit exceeded for ${identifierType}: ${identifier}. Current count: ${currentCount}/${autolockThreshold}`
|
||||
);
|
||||
|
||||
// If they have exceeded the threshold, lock them
|
||||
if (currentCount + 1 >= autolockThreshold) {
|
||||
log.warn(
|
||||
`Auto-locking ${identifierType}: ${identifier}. Threshold reached: ${
|
||||
currentCount + 1
|
||||
}/${autolockThreshold}`
|
||||
);
|
||||
await lockUser(identifierType, identifier);
|
||||
await redis.del(lockKey);
|
||||
return true;
|
||||
@@ -60,8 +79,10 @@ export async function handleAutoLock({
|
||||
return false;
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message === "No user found for this API key.") {
|
||||
log.error(`Error in auto-lock: No user found for API key: ${identifier}`);
|
||||
throw err;
|
||||
}
|
||||
log.error(`Error in auto-lock process: ${err instanceof Error ? err.message : String(err)}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -142,13 +163,10 @@ async function lockUser(identifierType: string, identifier: string) {
|
||||
}
|
||||
|
||||
if (user && process.env.NEXT_PUBLIC_SENTRY_DSN) {
|
||||
Sentry.setUser({
|
||||
id: user.id.toString(),
|
||||
log.warn("Auto-locking user due to rate limit exceeded", {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
username: user.username ?? undefined,
|
||||
username: user.username,
|
||||
});
|
||||
Sentry.setTag("admin_notify", true);
|
||||
Sentry.setTag("auto_lock", true);
|
||||
Sentry.captureMessage(`User ${user.email} has been locked due to suspicious activity.`, "warning");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user