localgreenchain/lib/security/rateLimit.ts
Claude 5ea8bab5c3
Add production deployment infrastructure (Agent 4)
- Docker: Multi-stage Dockerfile with security hardening, docker-compose
  for production and development environments
- Environment: Comprehensive .env.example with all config options,
  lib/config/env.ts for typed environment validation
- Logging: Structured JSON logging with request/response middleware
- Monitoring: Prometheus metrics endpoint, Grafana dashboard, health
  checks (liveness/readiness probes)
- Security: Security headers, rate limiting, CORS middleware
- CI/CD: GitHub Actions workflows for CI, production deploy, and
  preview deployments
- Error tracking: Sentry integration foundation

Files created:
- Docker: Dockerfile, docker-compose.yml, docker-compose.dev.yml, .dockerignore
- Config: lib/config/env.ts, lib/config/index.ts
- Logging: lib/logging/logger.ts, lib/logging/middleware.ts
- Monitoring: lib/monitoring/sentry.ts, lib/monitoring/metrics.ts,
  lib/monitoring/health.ts
- Security: lib/security/headers.ts, lib/security/rateLimit.ts,
  lib/security/cors.ts
- API: pages/api/health/*, pages/api/metrics.ts
- Infra: infra/prometheus/prometheus.yml, infra/grafana/*
2025-11-23 03:54:03 +00:00

151 lines
4 KiB
TypeScript

/**
* Rate Limiting Middleware
* Agent 4: Production Deployment
*
* Provides rate limiting for API routes to prevent abuse.
*/
import type { NextApiRequest, NextApiResponse, NextApiHandler } from 'next';
import { env } from '../config';
import { logger } from '../logging';
interface RateLimitConfig {
windowMs: number; // Time window in milliseconds
maxRequests: number; // Max requests per window
keyGenerator?: (req: NextApiRequest) => string;
skipSuccessfulRequests?: boolean;
skipFailedRequests?: boolean;
message?: string;
}
interface RateLimitEntry {
count: number;
resetTime: number;
}
// In-memory store for rate limiting
// In production, use Redis for distributed rate limiting
const store = new Map<string, RateLimitEntry>();
// Cleanup old entries periodically
setInterval(() => {
const now = Date.now();
for (const [key, entry] of store.entries()) {
if (entry.resetTime < now) {
store.delete(key);
}
}
}, 60000); // Cleanup every minute
const DEFAULT_CONFIG: RateLimitConfig = {
windowMs: env.rateLimitWindowMs,
maxRequests: env.rateLimitMaxRequests,
message: 'Too many requests, please try again later.',
};
/**
* Default key generator - uses IP address
*/
function defaultKeyGenerator(req: NextApiRequest): string {
const forwarded = req.headers['x-forwarded-for'];
if (typeof forwarded === 'string') {
return forwarded.split(',')[0].trim();
}
if (Array.isArray(forwarded)) {
return forwarded[0];
}
return req.socket?.remoteAddress || 'unknown';
}
/**
* Check if request is rate limited
*/
function checkRateLimit(
key: string,
config: RateLimitConfig
): { limited: boolean; remaining: number; resetTime: number } {
const now = Date.now();
let entry = store.get(key);
// Create new entry if doesn't exist or expired
if (!entry || entry.resetTime < now) {
entry = {
count: 0,
resetTime: now + config.windowMs,
};
store.set(key, entry);
}
// Increment count
entry.count += 1;
const remaining = Math.max(0, config.maxRequests - entry.count);
const limited = entry.count > config.maxRequests;
return { limited, remaining, resetTime: entry.resetTime };
}
/**
* Rate limiting middleware
*/
export function withRateLimit(
handler: NextApiHandler,
config: Partial<RateLimitConfig> = {}
): NextApiHandler {
const mergedConfig: RateLimitConfig = { ...DEFAULT_CONFIG, ...config };
const keyGenerator = mergedConfig.keyGenerator || defaultKeyGenerator;
return async (req: NextApiRequest, res: NextApiResponse) => {
const key = keyGenerator(req);
const { limited, remaining, resetTime } = checkRateLimit(key, mergedConfig);
// Set rate limit headers
res.setHeader('X-RateLimit-Limit', mergedConfig.maxRequests);
res.setHeader('X-RateLimit-Remaining', remaining);
res.setHeader('X-RateLimit-Reset', Math.ceil(resetTime / 1000));
if (limited) {
logger.warn('Rate limit exceeded', {
ip: key,
path: req.url,
method: req.method,
});
res.setHeader('Retry-After', Math.ceil((resetTime - Date.now()) / 1000));
return res.status(429).json({
error: 'Too Many Requests',
message: mergedConfig.message,
retryAfter: Math.ceil((resetTime - Date.now()) / 1000),
});
}
return handler(req, res);
};
}
/**
* Create a rate limiter with custom settings
*/
export function createRateLimiter(config: Partial<RateLimitConfig>) {
return (handler: NextApiHandler) => withRateLimit(handler, config);
}
/**
* Stricter rate limiter for authentication endpoints
*/
export const authRateLimiter = createRateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
maxRequests: 5, // 5 attempts per window
message: 'Too many authentication attempts. Please try again in 15 minutes.',
});
/**
* API rate limiter for general endpoints
*/
export const apiRateLimiter = createRateLimiter({
windowMs: 60 * 1000, // 1 minute
maxRequests: 60, // 60 requests per minute
});
// Export types
export type { RateLimitConfig, RateLimitEntry };