Files
calendar/apps/api/v1/lib/helpers/rateLimitApiKey.ts
T
1ffea493ec fix: use userId as ratelimit identifier (#18803)
* fix: use userId as ratelimit identifier

Using raw api keys as identifiers is problematic because they are logged
and are accessible via Unkey's UI.

My first idea was to use the api key's primary key, which would solve
this just fine. But after looking at the implementation it seemed easier
to use the user's id, which is what most people are interested in
limiting, not the actual keys. Also the userId was already passed in the
requests context, which made it easier. Sean confirmed that ratelimiting
the user, not their keys made more sense, so here we are.

* Update rateLimitApiKey.ts

* Update rateLimitApiKey.ts

* Fix tests

* Fix tests

---------

Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2025-01-22 21:37:30 -05:00

49 lines
1.7 KiB
TypeScript

import type { NextMiddleware } from "next-api-middleware";
import { handleAutoLock } from "@calcom/lib/autoLock";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { HttpError } from "@calcom/lib/http-error";
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
if (!req.userId) return res.status(401).json({ message: "No userId provided" });
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
// TODO: Add a way to add trusted api keys
try {
const identifier = req.userId.toString();
await checkRateLimitAndThrowError({
identifier,
rateLimitingType: "api",
onRateLimiterResponse: async (response) => {
res.setHeader("X-RateLimit-Limit", response.limit);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
try {
const didLock = await handleAutoLock({
identifier,
identifierType: "userId",
rateLimitResponse: response,
});
if (didLock) {
return res.status(429).json({ message: "Too many requests" });
}
} catch (error) {
if (error instanceof Error && error.message === "No user found for this API key.") {
return res.status(401).json({ message: error.message });
}
throw error;
}
},
});
} catch (error) {
if (error instanceof HttpError) {
return res.status(error.statusCode).json({ message: error.message });
}
return res.status(429).json({ message: "Rate limit exceeded" });
}
await next();
};