File size: 1,638 Bytes
5dfbe50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { Context, Next } from 'hono';
import { config } from '../config';

interface RateLimitStore {
  [key: string]: {
    count: number;
    resetTime: number;
  };
}

const store: RateLimitStore = {};

// Simple in-memory rate limiter
export const rateLimitMiddleware = async (c: Context, next: Next) => {
  const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown';
  const now = Date.now();
  const windowStart = now - config.rateLimit.windowMs;

  // Clean up old entries
  Object.keys(store).forEach(key => {
    if (store[key].resetTime < windowStart) {
      delete store[key];
    }
  });

  // Check rate limit
  if (!store[ip]) {
    store[ip] = { count: 1, resetTime: now + config.rateLimit.windowMs };
  } else if (store[ip].resetTime < now) {
    store[ip] = { count: 1, resetTime: now + config.rateLimit.windowMs };
  } else {
    store[ip].count++;
  }

  if (store[ip].count > config.rateLimit.max) {
    return c.json(
      { error: 'Too many requests', retryAfter: Math.ceil((store[ip].resetTime - now) / 1000) },
      429,
      {
        'Retry-After': Math.ceil((store[ip].resetTime - now) / 1000).toString(),
        'X-RateLimit-Limit': config.rateLimit.max.toString(),
        'X-RateLimit-Remaining': '0',
        'X-RateLimit-Reset': new Date(store[ip].resetTime).toISOString(),
      }
    );
  }

  // Add rate limit headers
  c.header('X-RateLimit-Limit', config.rateLimit.max.toString());
  c.header('X-RateLimit-Remaining', (config.rateLimit.max - store[ip].count).toString());
  c.header('X-RateLimit-Reset', new Date(store[ip].resetTime).toISOString());

  await next();
};