Skip to content

Commit

Permalink
implement rate limit
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjtalkshow committed Dec 12, 2024
1 parent c8b2b74 commit e2df3a0
Show file tree
Hide file tree
Showing 18 changed files with 246 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ CRON_SECRET=
# mainnet or sepolia
# Note: Not everything is supported on sepolia
# Default: mainnet
NEXT_PUBLIC_NETWORK=mainnet
NEXT_PUBLIC_NETWORK=mainnet
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -86,6 +89,7 @@
"prettier": "3.3.3",
"prisma": "5.18.0",
"tailwindcss": "3.3.0",
"ts-node": "^10.9.2",
"typescript": "5"
},
"engines": {
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/price/[name]/route.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
1 change: 1 addition & 0 deletions src/app/api/raffle/luckyWinner/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
3 changes: 3 additions & 0 deletions src/app/api/raffle/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
3 changes: 3 additions & 0 deletions src/app/api/referral/createUser/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/stats/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/stats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/strategies/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PoolInfo[]>((get) => {
const pools: PoolInfo[] = [];
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/tnc/getUser/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/app/api/tnc/signUser/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
2 changes: 2 additions & 0 deletions src/app/api/users/ognft/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Redis } from '@upstash/redis';

const redis = Redis.fromEnv();

export default redis;
42 changes: 42 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -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();
}
30 changes: 30 additions & 0 deletions src/scripts/test-rate-limit.ts
Original file line number Diff line number Diff line change
@@ -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);
5 changes: 5 additions & 0 deletions src/types/redis.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module 'lib/redis' {
import { Redis } from '@upstash/redis';
const redis: Redis;
export default redis;
}
11 changes: 11 additions & 0 deletions tsconfig.server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["scripts/**/*.ts"]
}
Loading

0 comments on commit e2df3a0

Please sign in to comment.