* 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>
49 lines
1.7 KiB
TypeScript
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();
|
|
};
|