rate-limiting

v2026.09.25

Add per-user and per-IP rate limiting to API endpoints. Covers Cloudflare Workers (KV-based sliding window), Express.js, Next.js, and generic patterns. Prevents abuse, brute-force attacks, and resource exhaustion. Use when adding rate limits, preventing API abuse, or fixing OWASP A04/A07 findings.

GitHub
安装命令
npx skhub add afu-it/rate-limiting
Markdown
SKILL.md

Rate Limiting

Add per-user rate limiting to prevent abuse from compromised accounts, bots, or malicious actors. Addresses OWASP A04 (Insecure Design) and A07 (Authentication Failures).

Why Rate Limiting Matters

Without rate limiting:

  • A stolen session token can drain all credits in seconds
  • A bot can create thousands of payment invoices
  • A single user can DoS your service
  • Brute-force attacks against auth endpoints succeed

Cloudflare Workers (KV-based Sliding Window)

Rate Limit Utility

// lib/rate-limit.ts
import { auditLog } from './audit';

export type RateLimitResult =
  | { allowed: true }
  | { allowed: false; retryAfterSeconds: number };

export async function checkRateLimit(
  kv: KVNamespace,
  userId: string,
  endpoint: string,
  maxRequests: number,
  windowSeconds = 60,
): Promise<RateLimitResult> {
  const window = Math.floor(Date.now() / (windowSeconds * 1000));
  const key = `ratelimit:${userId}:${endpoint}:${window}`;

  const current = await kv.get(key);
  const count = current ? parseInt(current, 10) : 0;

  if (count >= maxRequests) {
    auditLog('auth.unauthorized', {
      reason: 'rate_limit_exceeded',
      userId,
      endpoint,
      count,
      maxRequests,
    });
    return { allowed: false, retryAfterSeconds: windowSeconds };
  }

  await kv.put(key, String(count + 1), { expirationTtl: windowSeconds * 2 });
  return { allowed: true };
}

Usage in API Route

import { checkRateLimit } from '@/lib/rate-limit';

export async function POST() {
  const { userId } = await auth();
  if (!userId) return new Response('Unauthorized', { status: 401 });

  const { PALANGIC_GUEST_KV } = await getBindings();

  // Max 10 requests per minute
  const rateCheck = await checkRateLimit(PALANGIC_GUEST_KV, userId, 'credits-spend', 10, 60);
  if (!rateCheck.allowed) {
    return Response.json(
      { error: 'rate_limit_exceeded', retryAfter: rateCheck.retryAfterSeconds },
      { status: 429, headers: { 'Retry-After': String(rateCheck.retryAfterSeconds) } },
    );
  }

  // ... proceed with operation
}

Recommended Limits

EndpointLimitWindowRationale
Credit spend / export10/min60sNormal user exports 1-3 at a time
Payment creation5/min60sNo legitimate reason to create 5+ invoices/min
Login attempts5/min60sBrute-force prevention
Password reset3/hour3600sPrevent email bombing
API general60/min60sGeneral abuse prevention
Guest/anonymous1/day86400sFree tier limit

Express.js / Node.js

Using express-rate-limit

import rateLimit from 'express-rate-limit';

const apiLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 10,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.user?.id || req.ip,
  handler: (req, res) => {
    res.status(429).json({
      error: 'rate_limit_exceeded',
      retryAfter: 60,
    });
  },
});

app.use('/api/credits', apiLimiter);

Using Redis (production)

import { RateLimiterRedis } from 'rate-limiter-flexible';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

const rateLimiter = new RateLimiterRedis({
  storeClient: redis,
  keyPrefix: 'ratelimit',
  points: 10,       // max requests
  duration: 60,     // per 60 seconds
  blockDuration: 60, // block for 60s after exceeding
});

async function rateLimitMiddleware(req, res, next) {
  try {
    const key = req.user?.id || req.ip;
    await rateLimiter.consume(key);
    next();
  } catch (rejRes) {
    res.set('Retry-After', String(Math.ceil(rejRes.msBeforeNext / 1000)));
    res.status(429).json({ error: 'rate_limit_exceeded' });
  }
}

Next.js (App Router)

Middleware-based Rate Limiting

// middleware.ts
import { NextResponse } from 'next/server';

const rateMap = new Map<string, { count: number; resetAt: number }>();

export function middleware(request: Request) {
  // Only rate-limit API routes
  if (!request.url.includes('/api/')) return NextResponse.next();

  const ip = request.headers.get('x-forwarded-for') || 'unknown';
  const now = Date.now();
  const window = 60_000; // 1 minute
  const maxRequests = 60;

  const entry = rateMap.get(ip);
  if (!entry || now > entry.resetAt) {
    rateMap.set(ip, { count: 1, resetAt: now + window });
    return NextResponse.next();
  }

  if (entry.count >= maxRequests) {
    return NextResponse.json(
      { error: 'rate_limit_exceeded' },
      { status: 429, headers: { 'Retry-After': '60' } },
    );
  }

  entry.count++;
  return NextResponse.next();
}

Note: In-memory rate limiting only works for single-instance deployments. For serverless/edge, use KV, Redis, or Cloudflare Rate Limiting rules.

Response Headers

Always include standard rate limit headers:

const headers = {
  'Retry-After': '60',
  'X-RateLimit-Limit': '10',
  'X-RateLimit-Remaining': String(maxRequests - count),
  'X-RateLimit-Reset': String(Math.ceil(resetAt / 1000)),
};

Testing Rate Limits

test('rate limit blocks after max requests', async () => {
  // Make maxRequests calls
  for (let i = 0; i < 10; i++) {
    const res = await POST(makeRequest());
    expect(res.status).toBe(200);
  }

  // Next call should be blocked
  const blocked = await POST(makeRequest());
  expect(blocked.status).toBe(429);
  const body = await blocked.json();
  expect(body.error).toBe('rate_limit_exceeded');
  expect(body.retryAfter).toBeDefined();
});

Cloudflare Rate Limiting Rules (Dashboard)

For IP-based rate limiting without code changes:

  1. Cloudflare Dashboard → Security → WAF → Rate limiting rules
  2. Create rule:
    • Match: URI Path contains /api/
    • Rate: 100 requests per 1 minute
    • Per: IP address
    • Action: Block for 60 seconds

This provides a baseline defense even if application-level rate limiting has bugs.

PDPA 2024 Relevance

Rate limiting demonstrates:

  • Proportionate security measures -- preventing abuse shows due diligence
  • Data minimization -- limiting operations reduces exposure window
  • Incident prevention -- stops automated attacks before they cause data breaches

A system without rate limiting that gets brute-forced is evidence of negligence under PDPA 2024.

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.25

发布时间

2026年9月25日

分类

未分类

许可证

未指定

源路径

skills/rate-limiting

默认分支

main

最新提交

6aeeb8f

Tree SHA

14cb46d