Files
calendar/packages/lib/rateLimit.ts
T
883d745784 feat: SMS/Whatsapp to attendee for teams (#14648)
* remove event type requires confirmation

* allow sms sending also for team (frontend)

* allow text to attendee for team (backend)

* add rate limiting for SMS (WIP)

* add functionality to lock and unlock SMS sending (+admin UI view)

* rename to smsLockState

* add ability to lock users and teams

* don't send sms if locked

* add missing imports

* fix rate limit namespace

* add translation

* fix type error

* remove prop from UsersTable

* add smsLockState to buildUser

* code clean up

* create seperate sms rate limit function

* fix type error

* add missing userId and teamId to sendSMS

* code improvements

* add User Team type

* fix marking as reviewed

* check monthly sms limit only for user

* don't lock orgs

* fix type error

* fix avatars

* fix type error

* fix type error

* fix type error

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-23 19:48:44 +00:00

111 lines
2.7 KiB
TypeScript

import { Ratelimit, type LimitOptions, type RatelimitResponse } from "@unkey/ratelimit";
import { isIpInBanListString } from "./getIP";
import logger from "./logger";
const log = logger.getSubLogger({ prefix: ["RateLimit"] });
export type RateLimitHelper = {
rateLimitingType?: "core" | "forcedSlowMode" | "common" | "api" | "ai" | "sms" | "smsMonth";
identifier: string;
opts?: LimitOptions;
/**
* Using a callback instead of a regular return to provide headers even
* when the rate limit is reached and an error is thrown.
**/
onRateLimiterResponse?: (response: RatelimitResponse) => void;
};
let warningDisplayed = false;
/** Prevent flooding the logs while testing/building */
function logOnce(message: string) {
if (warningDisplayed) return;
log.warn(message);
warningDisplayed = true;
}
export const API_KEY_RATE_LIMIT = 30;
export function rateLimiter() {
const { UNKEY_ROOT_KEY } = process.env;
if (!UNKEY_ROOT_KEY) {
logOnce("Disabled due to not finding UNKEY_ROOT_KEY env variable");
return () => ({ success: true, limit: 10, remaining: 999, reset: 0 } as RatelimitResponse);
}
const timeout = {
fallback: { success: true, limit: 10, remaining: 999, reset: 0 },
ms: 5000,
};
const limiter = {
core: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "core",
limit: 10,
duration: "60s",
async: true,
timeout,
}),
common: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "common",
limit: 200,
duration: "60s",
async: true,
timeout,
}),
forcedSlowMode: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "forcedSlowMode",
limit: 1,
duration: "30s",
async: true,
timeout,
}),
api: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "api",
limit: API_KEY_RATE_LIMIT,
duration: "60s",
async: true,
timeout,
}),
ai: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "ai",
limit: 20,
duration: "1d",
async: true,
timeout,
}),
sms: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "sms",
limit: 50,
duration: "5m",
async: true,
timeout,
}),
smsMonth: new Ratelimit({
rootKey: UNKEY_ROOT_KEY,
namespace: "smsMonth",
limit: 250,
duration: "30d",
async: true,
timeout,
}),
};
async function rateLimit({ rateLimitingType = "core", identifier, opts }: RateLimitHelper) {
if (isIpInBanListString(identifier)) {
return await limiter.forcedSlowMode.limit(identifier, opts);
}
return await limiter[rateLimitingType].limit(identifier, opts);
}
return rateLimit;
}