Skip to content

Commit

Permalink
refactor: update rate limit logic and add test
Browse files Browse the repository at this point in the history
  • Loading branch information
pierrevano committed Jan 19, 2025
1 parent 7c95c61 commit 2a0a67b
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 191 deletions.
82 changes: 2 additions & 80 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@ require("dotenv").config();
const cors = require("cors");
const express = require("express");
const path = require("path");
const { RateLimiterMemory } = require("rate-limiter-flexible");

const app = express();
const PORT = process.env.PORT || 8081;

const { config } = require("./src/config");
const { sendInternalError, sendResponse } = require("./src/utils/sendRequest");
const { limiter } = require("./src/utils/rateLimiter");
const { sendInternalError } = require("./src/utils/sendRequest");
const {
getUserPreferences,
saveOrUpdateUserPreferences,
} = require("./src/routes/getOrSaveUserPreferences");
const getId = require("./src/routes/getId");
const getItems = require("./src/routes/getItems");
const { sendToNewRelic } = require("./src/utils/sendToNewRelic");

// Use CORS middleware
app.use(cors());
Expand All @@ -28,82 +26,6 @@ app.use(express.static(path.join(__dirname, "public")));

app.use(express.json());

// Initialize rate limiter with in-memory storage
const createRateLimiter = (points) =>
new RateLimiterMemory({
points: points, // maximum number of requests
duration: config.duration, // window duration in seconds
blockDuration: config.blockDuration, // block duration in seconds if rate limit is exceeded
});

// Middleware to apply rate limiting
const defaultLimiter = createRateLimiter(config.points);
const higherLimiter = createRateLimiter(config.higher_points);

const getRateLimiterKey = (req) => {
const forwardedFor = req.headers["x-forwarded-for"];
const ip = req.ip;
const userAgent = req.headers["user-agent"] || "unknown";

const ipOrForwardedFor = forwardedFor
? forwardedFor.split(",")[0].trim()
: ip;

return `${ipOrForwardedFor}-${userAgent}`;
};

const limiter = (req, res, next) => {
const isInternalApiKeyValid =
req.query.api_key !== undefined &&
config.internalApiKey !== undefined &&
req.query.api_key === config.internalApiKey;
console.log("Internal API key:", config.internalApiKey);
console.log("API key validity:", isInternalApiKeyValid);

const rateLimiter = isInternalApiKeyValid ? higherLimiter : defaultLimiter;

if (isInternalApiKeyValid) {
return next(); // Ignore rate limiting for internal API key
}

rateLimiter
.consume(getRateLimiterKey(req))
.then((rateLimiterRes) => {
const rateLimitHeaders = {
"X-RateLimit-Limit":
rateLimiterRes.remainingPoints + rateLimiterRes.consumedPoints,
"X-RateLimit-Remaining": rateLimiterRes.remainingPoints,
"X-RateLimit-Reset": new Date(
Date.now() + rateLimiterRes.msBeforeNext,
).toISOString(),
};

console.log("Rate Limit Headers:", rateLimitHeaders);

res.set(rateLimitHeaders);

sendToNewRelic(req, null, null, rateLimitHeaders);

next(); // Allow the request if within the limit
})
.catch((rateLimiterRes) => {
const rateLimitHeaders = {
"Retry-After": Math.ceil(rateLimiterRes.msBeforeNext / 1000),
};

console.log("Rate Limit Headers on error:", rateLimitHeaders);

res.set(rateLimitHeaders);

sendToNewRelic(req, null, null, rateLimitHeaders);

sendResponse(res, 429, {
message:
"Too many requests. Please try again later. If you need an API key for higher limits, contact me on: https://pierrevano.github.io",
});
});
};

/* A route that is used to get the data for all items. */
app.get("/", limiter, getItems);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "whatson-api",
"version": "2.90.1",
"version": "2.90.2",
"description": "What's on? API to retrieve movies and tvshows",
"main": "index.js",
"scripts": {
Expand Down
Loading

0 comments on commit 2a0a67b

Please sign in to comment.