phase 1-6 implemented
This commit is contained in:
1 parent
22c274bb83
commit
2c4927478a
73 files changed
+9702
-2354
No files matched your search
@@ -0,0 +1,121 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
interface RateLimitStore {
|
||||
[key: string]: {
|
||||
count: number;
|
||||
resetTime: number;
|
||||
};
|
||||
}
|
||||
|
||||
const store: RateLimitStore = {};
|
||||
|
||||
interface RateLimitConfig {
|
||||
interval: number; // Time window in milliseconds
|
||||
maxRequests: number; // Maximum requests in time window
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple in-memory rate limiter
|
||||
* For production, use Redis or a dedicated rate limiting service
|
||||
*
|
||||
* @param identifier - Unique identifier (e.g., IP address, user ID)
|
||||
* @param config - Rate limit configuration
|
||||
* @returns NextResponse error if rate limited, undefined if allowed
|
||||
*
|
||||
* @example
|
||||
* const rateLimitError = rateLimit(userId, { interval: 60000, maxRequests: 5 });
|
||||
* if (rateLimitError) return rateLimitError;
|
||||
*/
|
||||
export function rateLimit(
|
||||
identifier: string,
|
||||
config: RateLimitConfig,
|
||||
): NextResponse | undefined {
|
||||
const now = Date.now();
|
||||
const key = identifier;
|
||||
|
||||
// Initialize or reset if window expired
|
||||
if (!store[key] || now > store[key].resetTime) {
|
||||
store[key] = {
|
||||
count: 1,
|
||||
resetTime: now + config.interval,
|
||||
};
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Increment count
|
||||
store[key].count++;
|
||||
|
||||
// Check if limit exceeded
|
||||
if (store[key].count > config.maxRequests) {
|
||||
const retryAfter = Math.ceil((store[key].resetTime - now) / 1000);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Too many requests",
|
||||
message: `Rate limit exceeded. Try again in ${retryAfter} seconds.`,
|
||||
retryAfter,
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
"Retry-After": retryAfter.toString(),
|
||||
"X-RateLimit-Limit": config.maxRequests.toString(),
|
||||
"X-RateLimit-Remaining": "0",
|
||||
"X-RateLimit-Reset": store[key].resetTime.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limit configurations for different endpoints
|
||||
*/
|
||||
export const RATE_LIMITS = {
|
||||
// Authentication endpoints - stricter limits
|
||||
login: { interval: 60000, maxRequests: 5 }, // 5 attempts per minute
|
||||
register: { interval: 3600000, maxRequests: 3 }, // 3 attempts per hour
|
||||
passwordReset: { interval: 3600000, maxRequests: 3 }, // 3 attempts per hour
|
||||
|
||||
// API endpoints - more lenient
|
||||
api: { interval: 60000, maxRequests: 100 }, // 100 requests per minute
|
||||
apiStrict: { interval: 60000, maxRequests: 10 }, // 10 requests per minute
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Get client identifier for rate limiting
|
||||
* Uses IP address as fallback if user is not authenticated
|
||||
*
|
||||
* @param request - NextRequest object
|
||||
* @param userId - Optional authenticated user ID
|
||||
* @returns Identifier for rate limiting
|
||||
*/
|
||||
export function getClientIdentifier(request: Request, userId?: string): string {
|
||||
if (userId) return `user:${userId}`;
|
||||
|
||||
// Try to get IP from headers (works with most reverse proxies)
|
||||
const forwarded = request.headers.get("x-forwarded-for");
|
||||
const ip =
|
||||
forwarded?.split(",")[0] ?? request.headers.get("x-real-ip") ?? "unknown";
|
||||
|
||||
return `ip:${ip}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup expired rate limit entries (call periodically)
|
||||
*/
|
||||
export function cleanupRateLimitStore(): void {
|
||||
const now = Date.now();
|
||||
for (const key in store) {
|
||||
if (store[key].resetTime < now) {
|
||||
delete store[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup every 10 minutes
|
||||
if (typeof setInterval !== "undefined") {
|
||||
setInterval(cleanupRateLimitStore, 10 * 60 * 1000);
|
||||
}
|
||||
Reference in new issue
Block a user