Add API rate limit (#23)
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
"@hono/swagger-ui": "^0.2.1",
|
||||
"@hono/zod-openapi": "^0.10.0",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@prisma/client": "^5.11.0",
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"@tanstack/react-query": "^5.25.0",
|
||||
|
@@ -40,6 +40,10 @@ export const env = createEnv({
|
||||
GOOGLE_CLIENT_SECRET: z.string(),
|
||||
SES_QUEUE_LIMIT: z.string().transform((str) => parseInt(str, 10)),
|
||||
AWS_DEFAULT_REGION: z.string().default("us-east-1"),
|
||||
API_RATE_LIMIT: z
|
||||
.string()
|
||||
.transform((str) => parseInt(str, 10))
|
||||
.default(2),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -72,6 +76,7 @@ export const env = createEnv({
|
||||
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
|
||||
SES_QUEUE_LIMIT: process.env.SES_QUEUE_LIMIT,
|
||||
AWS_DEFAULT_REGION: process.env.AWS_DEFAULT_REGION,
|
||||
API_RATE_LIMIT: process.env.API_RATE_LIMIT,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
|
@@ -1,20 +1,30 @@
|
||||
import TTLCache from "@isaacs/ttlcache";
|
||||
import { Context } from "hono";
|
||||
import { hashToken } from "../auth";
|
||||
import { db } from "../db";
|
||||
import { UnsendApiError } from "./api-error";
|
||||
import { env } from "~/env";
|
||||
|
||||
const rateLimitCache = new TTLCache({
|
||||
ttl: 1000, // 1 second
|
||||
max: env.API_RATE_LIMIT,
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the team from the token. Also will check if the token is valid.
|
||||
*/
|
||||
export const getTeamFromToken = async (c: Context) => {
|
||||
const authHeader = c.req.header("Authorization");
|
||||
|
||||
if (!authHeader) {
|
||||
throw new UnsendApiError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "No Authorization header provided",
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.split(" ")[1];
|
||||
|
||||
if (!token) {
|
||||
throw new UnsendApiError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -22,6 +32,8 @@ export const getTeamFromToken = async (c: Context) => {
|
||||
});
|
||||
}
|
||||
|
||||
checkRateLimit(token);
|
||||
|
||||
const hashedToken = hashToken(token);
|
||||
|
||||
const team = await db.team.findFirst({
|
||||
@@ -55,3 +67,18 @@ export const getTeamFromToken = async (c: Context) => {
|
||||
|
||||
return team;
|
||||
};
|
||||
|
||||
const checkRateLimit = (token: string) => {
|
||||
let rateLimit = rateLimitCache.get<number>(token);
|
||||
|
||||
rateLimit = rateLimit ?? 0;
|
||||
|
||||
if (rateLimit >= 2) {
|
||||
throw new UnsendApiError({
|
||||
code: "RATE_LIMITED",
|
||||
message: `Rate limit exceeded, ${env.API_RATE_LIMIT} requests per second`,
|
||||
});
|
||||
}
|
||||
|
||||
rateLimitCache.set(token, rateLimit + 1);
|
||||
};
|
||||
|
Reference in New Issue
Block a user