From e2df3a0971d2cb186ae9d870789b10702fae769b Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Thu, 12 Dec 2024 22:05:55 +0100 Subject: [PATCH 1/5] implement rate limit --- .env.sample | 4 +- package.json | 6 +- src/app/api/price/[name]/route.ts | 2 + src/app/api/raffle/luckyWinner/route.ts | 1 + src/app/api/raffle/route.ts | 3 + src/app/api/referral/createUser/route.ts | 3 + src/app/api/stats/[address]/route.ts | 2 + src/app/api/stats/route.ts | 2 + src/app/api/strategies/route.ts | 2 + src/app/api/tnc/getUser/[address]/route.ts | 2 + src/app/api/tnc/signUser/route.ts | 3 + src/app/api/users/ognft/[address]/route.ts | 2 + src/lib/redis.ts | 5 + src/middleware.ts | 42 +++++++ src/scripts/test-rate-limit.ts | 30 +++++ src/types/redis.d.ts | 5 + tsconfig.server.json | 11 ++ yarn.lock | 124 ++++++++++++++++++++- 18 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 src/lib/redis.ts create mode 100644 src/middleware.ts create mode 100644 src/scripts/test-rate-limit.ts create mode 100644 src/types/redis.d.ts create mode 100644 tsconfig.server.json diff --git a/.env.sample b/.env.sample index f5e2a704..0c44ca70 100644 --- a/.env.sample +++ b/.env.sample @@ -16,4 +16,6 @@ CRON_SECRET= # mainnet or sepolia # Note: Not everything is supported on sepolia # Default: mainnet -NEXT_PUBLIC_NETWORK=mainnet \ No newline at end of file +NEXT_PUBLIC_NETWORK=mainnet +UPSTASH_REDIS_REST_URL= +UPSTASH_REDIS_REST_TOKEN= \ No newline at end of file diff --git a/package.json b/package.json index 7223e3ad..1dd91804 100755 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "format:check": "prettier --check \"**/*.{ts,tsx,json}\"", "format:fix": "prettier --write \"**/*.{ts,tsx,json}\"", "prepare": "husky", - "postinstall": "prisma generate" + "postinstall": "prisma generate", + "test:ratelimit": "ts-node --project tsconfig.server.json src/scripts/test-rate-limit.ts" }, "files": [ "CHANGELOG.md", @@ -34,6 +35,8 @@ "@tanstack/query-core": "5.28.0", "@types/mixpanel-browser": "2.49.0", "@types/mustache": "4.2.5", + "@upstash/ratelimit": "^2.0.5", + "@upstash/redis": "^1.34.3", "@vercel/analytics": "1.2.2", "@vercel/speed-insights": "1.0.12", "axios": "1.6.7", @@ -86,6 +89,7 @@ "prettier": "3.3.3", "prisma": "5.18.0", "tailwindcss": "3.3.0", + "ts-node": "^10.9.2", "typescript": "5" }, "engines": { diff --git a/src/app/api/price/[name]/route.ts b/src/app/api/price/[name]/route.ts index 6e5c759b..af47099d 100644 --- a/src/app/api/price/[name]/route.ts +++ b/src/app/api/price/[name]/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; export const revalidate = 300; // 5 mins +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; // only meant for backend calls async function initRedis() { diff --git a/src/app/api/raffle/luckyWinner/route.ts b/src/app/api/raffle/luckyWinner/route.ts index 16f60044..272ba760 100644 --- a/src/app/api/raffle/luckyWinner/route.ts +++ b/src/app/api/raffle/luckyWinner/route.ts @@ -3,6 +3,7 @@ import { Raffle } from '@prisma/client'; import { NextResponse } from 'next/server'; export const dynamic = 'force-dynamic'; // static by default, unless reading the request +export const runtime = 'nodejs'; export async function GET(request: Request) { const authHeader = request.headers.get('authorization'); diff --git a/src/app/api/raffle/route.ts b/src/app/api/raffle/route.ts index d4182be3..67154c8e 100644 --- a/src/app/api/raffle/route.ts +++ b/src/app/api/raffle/route.ts @@ -4,6 +4,9 @@ import { db } from '@/db'; import { getStrategies } from '@/store/strategies.atoms'; import { standariseAddress } from '@/utils'; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + export async function POST(req: Request) { const { address, type } = await req.json(); diff --git a/src/app/api/referral/createUser/route.ts b/src/app/api/referral/createUser/route.ts index 9f6ade5d..9523dfdf 100644 --- a/src/app/api/referral/createUser/route.ts +++ b/src/app/api/referral/createUser/route.ts @@ -3,6 +3,9 @@ import { NextResponse } from 'next/server'; import { db } from '@/db'; import { standariseAddress } from '@/utils'; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + function isSixDigitAlphanumeric(str: string) { const regex = /^[a-zA-Z0-9]{6}$/; return regex.test(str); diff --git a/src/app/api/stats/[address]/route.ts b/src/app/api/stats/[address]/route.ts index c7369b10..83615c7b 100755 --- a/src/app/api/stats/[address]/route.ts +++ b/src/app/api/stats/[address]/route.ts @@ -3,6 +3,8 @@ import { standariseAddress } from '@/utils'; import { NextResponse } from 'next/server'; export const revalidate = 0; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; export async function GET(_req: Request, context: any) { const { params } = context; diff --git a/src/app/api/stats/route.ts b/src/app/api/stats/route.ts index c7c7b4f7..01e75855 100755 --- a/src/app/api/stats/route.ts +++ b/src/app/api/stats/route.ts @@ -2,6 +2,8 @@ import { getStrategies } from '@/store/strategies.atoms'; import { NextResponse } from 'next/server'; export const revalidate = 1800; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; export async function GET(_req: Request) { const strategies = getStrategies(); diff --git a/src/app/api/strategies/route.ts b/src/app/api/strategies/route.ts index 5b075580..4dcd8b02 100755 --- a/src/app/api/strategies/route.ts +++ b/src/app/api/strategies/route.ts @@ -11,6 +11,8 @@ import { IStrategy, NFTInfo, TokenInfo } from '@/strategies/IStrategy'; import { STRKFarmStrategyAPIResult } from '@/store/strkfarm.atoms'; export const revalidate = 3600; // 1 hr +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; const allPoolsAtom = atom((get) => { const pools: PoolInfo[] = []; diff --git a/src/app/api/tnc/getUser/[address]/route.ts b/src/app/api/tnc/getUser/[address]/route.ts index 6647c810..6fe5ea53 100644 --- a/src/app/api/tnc/getUser/[address]/route.ts +++ b/src/app/api/tnc/getUser/[address]/route.ts @@ -2,6 +2,8 @@ import { NextResponse } from 'next/server'; import { db } from '@/db'; import { standariseAddress } from '@/utils'; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; export async function GET(req: Request, context: any) { const { params } = context; diff --git a/src/app/api/tnc/signUser/route.ts b/src/app/api/tnc/signUser/route.ts index c127acaf..0b86e8e9 100644 --- a/src/app/api/tnc/signUser/route.ts +++ b/src/app/api/tnc/signUser/route.ts @@ -8,6 +8,9 @@ import { toBigInt } from 'ethers'; import Mixpanel from 'mixpanel'; const mixpanel = Mixpanel.init('118f29da6a372f0ccb6f541079cad56b'); +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + export async function POST(req: Request) { const { address, signature } = await req.json(); diff --git a/src/app/api/users/ognft/[address]/route.ts b/src/app/api/users/ognft/[address]/route.ts index 28760a7d..05b0b1a5 100644 --- a/src/app/api/users/ognft/[address]/route.ts +++ b/src/app/api/users/ognft/[address]/route.ts @@ -4,6 +4,8 @@ import { standariseAddress } from '../../../../../utils'; import OGNFTUsersJson from '../../../../../../public/og_nft_eligible_users.json'; export const revalidate = 3600; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; export async function GET(req: Request, context: any) { try { diff --git a/src/lib/redis.ts b/src/lib/redis.ts new file mode 100644 index 00000000..662053f7 --- /dev/null +++ b/src/lib/redis.ts @@ -0,0 +1,5 @@ +import { Redis } from '@upstash/redis'; + +const redis = Redis.fromEnv(); + +export default redis; diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 00000000..0559846d --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { Ratelimit } from '@upstash/ratelimit'; +import redis from './lib/redis'; + +export const config = { + matcher: ['/api/:path*'], +}; + +const ratelimit = new Ratelimit({ + redis, + limiter: Ratelimit.slidingWindow(20, '10 s'), + prefix: '@upstash/ratelimit', + analytics: true, +}); + +export async function middleware(request: NextRequest) { + const ip = request.headers.get('x-forwarded-for') || '127.0.0.1'; + const identifier = ip; + + let success, limit, remaining, reset; + try { + const result = await ratelimit.limit(identifier); + success = result.success; + limit = result.limit; + remaining = result.remaining; + reset = result.reset; + } catch (error) { + return NextResponse.json( + { message: 'Internal Server Error' }, + { status: 500 }, + ); + } + + if (!success) { + return NextResponse.json( + { message: 'Rate limit exceeded', limit, remaining, reset }, + { status: 429 }, + ); + } + + return NextResponse.next(); +} diff --git a/src/scripts/test-rate-limit.ts b/src/scripts/test-rate-limit.ts new file mode 100644 index 00000000..dc4bfd37 --- /dev/null +++ b/src/scripts/test-rate-limit.ts @@ -0,0 +1,30 @@ +async function testRateLimit(url: string, attempts: number) { + console.log(`Testing rate limit for ${url}`); + for (let i = 0; i < attempts; i++) { + const response = await fetch(url); + const remaining = response.headers.get('X-RateLimit-Remaining'); + console.log( + `Attempt ${i + 1}: Status ${response.status}, Remaining: ${remaining}`, + ); + await new Promise((resolve) => setTimeout(resolve, 100)); // Wait 100ms between requests + } + console.log('\n'); +} + +async function runTests() { + const baseUrl = 'http://localhost:3000/api'; + await testRateLimit(`${baseUrl}/price`, 25); + await testRateLimit(`${baseUrl}/raffle`, 25); + await testRateLimit(`${baseUrl}/raffle/luckyWinner`, 25); + await testRateLimit(`${baseUrl}/referral/createUser`, 25); + await testRateLimit(`${baseUrl}/stats`, 25); + await testRateLimit(`${baseUrl}/stats/[address]`, 25); + await testRateLimit(`${baseUrl}/strategies`, 25); + await testRateLimit(`${baseUrl}/tnc/getUser`, 25); + await testRateLimit(`${baseUrl}/tnc/getUser/[address]`, 25); + await testRateLimit(`${baseUrl}/tnc/signUser`, 25); + await testRateLimit(`${baseUrl}/users/ognft`, 25); + await testRateLimit(`${baseUrl}/users/ognft/[address]`, 25); +} + +runTests().catch(console.error); diff --git a/src/types/redis.d.ts b/src/types/redis.d.ts new file mode 100644 index 00000000..5b1a0702 --- /dev/null +++ b/src/types/redis.d.ts @@ -0,0 +1,5 @@ +declare module 'lib/redis' { + import { Redis } from '@upstash/redis'; + const redis: Redis; + export default redis; +} diff --git a/tsconfig.server.json b/tsconfig.server.json new file mode 100644 index 00000000..d0d810e8 --- /dev/null +++ b/tsconfig.server.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["scripts/**/*.ts"] +} diff --git a/yarn.lock b/yarn.lock index fd0bb2bc..20838081 100644 --- a/yarn.lock +++ b/yarn.lock @@ -949,6 +949,13 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@cypress/request-promise@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@cypress/request-promise/-/request-promise-5.0.0.tgz#86899e097dba5123c546f1cadb9bff70e2654ebe" @@ -1509,7 +1516,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@^3.1.0": +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== @@ -1524,6 +1531,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" @@ -2170,6 +2185,26 @@ resolved "https://registry.yarnpkg.com/@trpc/server/-/server-10.45.2.tgz#5f2778c4810f93b5dc407146334f8da70a0b51fb" integrity sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg== +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -2417,6 +2452,27 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@upstash/core-analytics@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@upstash/core-analytics/-/core-analytics-0.0.10.tgz#e686a313ec2279d5a8d53e6c215085f1c0f5ab4b" + integrity sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ== + dependencies: + "@upstash/redis" "^1.28.3" + +"@upstash/ratelimit@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@upstash/ratelimit/-/ratelimit-2.0.5.tgz#0e8e693b79bdcf5d8643c38bebe8b76e7b79b54a" + integrity sha512-1FRv0cs3ZlBjCNOCpCmKYmt9BYGIJf0J0R3pucOPE88R21rL7jNjXG+I+rN/BVOvYJhI9niRAS/JaSNjiSICxA== + dependencies: + "@upstash/core-analytics" "^0.0.10" + +"@upstash/redis@^1.28.3", "@upstash/redis@^1.34.3": + version "1.34.3" + resolved "https://registry.yarnpkg.com/@upstash/redis/-/redis-1.34.3.tgz#df0338f4983bba5141878e851be4fced494b44a0" + integrity sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ== + dependencies: + crypto-js "^4.2.0" + "@vercel/analytics@1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-1.2.2.tgz#715d8f203a170c06ba36b363e03b048c03060d5d" @@ -2712,6 +2768,18 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + acorn@^8.11.3, acorn@^8.9.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" @@ -2798,6 +2866,11 @@ anymatch@^3.1.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + arg@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" @@ -3528,6 +3601,11 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -3542,6 +3620,11 @@ crossws@^0.2.4: resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.2.4.tgz#82a8b518bff1018ab1d21ced9e35ffbe1681ad03" integrity sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg== +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + css-box-model@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" @@ -3716,6 +3799,11 @@ didyoumean@^1.2.2: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5574,6 +5662,11 @@ lru-cache@^10.0.1, lru-cache@^10.2.0, lru-cache@^10.4.3: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + memoize-one@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" @@ -7406,6 +7499,25 @@ ts-mixer@^6.0.3: resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -7682,6 +7794,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -7927,6 +8044,11 @@ yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From ac0237aeb315c1ed5f95d857bb805805ac23f256 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Tue, 17 Dec 2024 14:35:54 +0100 Subject: [PATCH 2/5] configure tokens and window with env variables && Include rate-limiting details in response headers --- src/middleware.ts | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 0559846d..63390539 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -6,11 +6,20 @@ export const config = { matcher: ['/api/:path*'], }; +const RATE_LIMIT_REQUESTS = parseInt( + process.env.RATE_LIMIT_REQUESTS || '20', + 10, +); +const RATE_LIMIT_WINDOW = process.env.RATE_LIMIT_WINDOW || '10 s'; + const ratelimit = new Ratelimit({ redis, - limiter: Ratelimit.slidingWindow(20, '10 s'), - prefix: '@upstash/ratelimit', + limiter: Ratelimit.slidingWindow( + RATE_LIMIT_REQUESTS, + RATE_LIMIT_WINDOW as `${number} s`, + ), analytics: true, + prefix: '@upstash/ratelimit', }); export async function middleware(request: NextRequest) { @@ -31,12 +40,16 @@ export async function middleware(request: NextRequest) { ); } - if (!success) { - return NextResponse.json( - { message: 'Rate limit exceeded', limit, remaining, reset }, - { status: 429 }, - ); - } + const response = success + ? NextResponse.next() + : NextResponse.json( + { message: 'Rate limit exceeded', limit, remaining, reset }, + { status: 429 }, + ); + + response.headers.set('X-RateLimit-Limit', limit.toString()); + response.headers.set('X-RateLimit-Remaining', remaining.toString()); + response.headers.set('X-RateLimit-Reset', reset.toString()); - return NextResponse.next(); + return response; } From 1c2191d12861cd5e89a0d2bd55994448c117b7df Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Tue, 17 Dec 2024 14:36:35 +0100 Subject: [PATCH 3/5] configure tokens and window with env variables && Include rate-limiting details in response headers --- .env.sample | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 0c44ca70..010af634 100644 --- a/.env.sample +++ b/.env.sample @@ -18,4 +18,7 @@ CRON_SECRET= # Default: mainnet NEXT_PUBLIC_NETWORK=mainnet UPSTASH_REDIS_REST_URL= -UPSTASH_REDIS_REST_TOKEN= \ No newline at end of file +UPSTASH_REDIS_REST_TOKEN= +# Rate limiting configuration +RATE_LIMIT_REQUESTS=50 +RATE_LIMIT_WINDOW=60 s \ No newline at end of file From 64052f207fdbef8e4f0527de8daa10a09fc532d5 Mon Sep 17 00:00:00 2001 From: EjembiEmmanuel Date: Thu, 19 Dec 2024 12:12:09 +0100 Subject: [PATCH 4/5] chore: update env.sample --- .env.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 010af634..d6756e18 100644 --- a/.env.sample +++ b/.env.sample @@ -21,4 +21,4 @@ UPSTASH_REDIS_REST_URL= UPSTASH_REDIS_REST_TOKEN= # Rate limiting configuration RATE_LIMIT_REQUESTS=50 -RATE_LIMIT_WINDOW=60 s \ No newline at end of file +RATE_LIMIT_WINDOW="60 s" \ No newline at end of file From 40357d32b6dd1fe43185f3654e6d07c2fc1bbb9c Mon Sep 17 00:00:00 2001 From: EjembiEmmanuel Date: Thu, 19 Dec 2024 12:17:30 +0100 Subject: [PATCH 5/5] fix: fix liniting issues --- src/middleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middleware.ts b/src/middleware.ts index 63390539..5f906de8 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -34,6 +34,7 @@ export async function middleware(request: NextRequest) { remaining = result.remaining; reset = result.reset; } catch (error) { + console.log(error); return NextResponse.json( { message: 'Internal Server Error' }, { status: 500 },