Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /**
* Redis-based distributed rate limiting
*
* This module provides a scalable rate limiting solution using Upstash Redis.
* Falls back to in-memory rate limiting if Redis is not configured.
*
* To enable Redis rate limiting:
* 1. Create an Upstash Redis database at https://upstash.com
* 2. Add these environment variables:
* - UPSTASH_REDIS_REST_URL
* - UPSTASH_REDIS_REST_TOKEN
* 3. Install the package: npm install @upstash/redis @upstash/ratelimit
*/
import { checkRateLimit as checkInMemoryRateLimit, getRateLimitInfo as getInMemoryRateLimitInfo } from "./rate-limit";
import { logger } from '@/lib/logging';
// Rate limit configurations for different actions
export const RATE_LIMIT_CONFIG = {
signup: { requests: 5, window: "1h" as const },
login: { requests: 10, window: "15m" as const },
forgotPassword: { requests: 5, window: "1h" as const },
resetPassword: { requests: 10, window: "15m" as const },
resendVerification: { requests: 3, window: "1h" as const },
api: { requests: 100, window: "15m" as const },
payment: { requests: 10, window: "1m" as const } } as const;
type RateLimitAction = keyof typeof RATE_LIMIT_CONFIG;
// Convert window string to milliseconds for in-memory fallback
const windowToMs: Record<string, number> = {
"1m": 60 * 1000,
"15m": 15 * 60 * 1000,
"1h": 60 * 60 * 1000,
"24h": 24 * 60 * 60 * 1000 };
interface RateLimitResult {
success: boolean;
remaining: number;
reset: number;
limit: number;
}
// Redis client (lazily initialized)
// let _redisClient: unknown = null;
const rateLimiters: Map<string, unknown> = new Map();
let redisAvailable: boolean | null = null;
/**
* Initialize Redis connection (if configured)
*
* Note: Redis rate limiting is disabled by default.
* To enable, install @upstash/redis and @upstash/ratelimit packages
* and set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN env vars.
*/
async function initRedis(): Promise<boolean> {
if (redisAvailable !== null) {
return redisAvailable;
}
const url = process.env.UPSTASH_REDIS_REST_URL;
const token = process.env.UPSTASH_REDIS_REST_TOKEN;
if (!url || !token) {
// Redis not configured - silently use in-memory rate limiting
redisAvailable = false;
return false;
}
// Redis is configured but we need the optional packages
// For now, always use in-memory until packages are installed
// To enable Redis: npm install @upstash/redis @upstash/ratelimit
// Then uncomment the dynamic import code below
logger.info("Redis configured but packages not installed, using in-memory rate limiting", { category: "RATE_LIMIT" });
redisAvailable = false;
return false;
/*
* Uncomment this block after installing @upstash/redis @upstash/ratelimit:
*
try {
const { Redis } = await import("@upstash/redis");
const { Ratelimit } = await import("@upstash/ratelimit");
redisClient = new Redis({ url, token });
// Create rate limiters for each action
for (const [action, config] of Object.entries(RATE_LIMIT_CONFIG)) {
const limiter = new Ratelimit({
redis: redisClient,
limiter: Ratelimit.slidingWindow(config.requests, config.window),
prefix: `ratelimit:${action}`,
analytics: true });
rateLimiters.set(action, limiter);
}
logger.info("RATE_LIMIT", "Redis rate limiting initialized");
redisAvailable = true;
return true;
} catch (error) {
logger.warn("RATE_LIMIT", "Failed to initialize Redis, falling back to in-memory", error);
redisAvailable = false;
return false;
}
*/
}
/**
* Check rate limit for an action
*/
export async function checkDistributedRateLimit(
action: RateLimitAction,
identifier: string
): Promise<RateLimitResult> {
const isRedisAvailable = await initRedis();
if (isRedisAvailable && rateLimiters.has(action)) {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const limiter = rateLimiters.get(action) as any;
const result = await limiter.limit(identifier);
return {
success: result.success,
remaining: result.remaining,
reset: result.reset,
limit: result.limit };
} catch (error) {
logger.error("Redis rate limit check failed", error instanceof Error ? error : new Error(String(error)), { category: "RATE_LIMIT" });
// Fall through to in-memory
}
}
// Fallback to in-memory
const config = RATE_LIMIT_CONFIG[action];
const windowMs = windowToMs[config.window] || 15 * 60 * 1000;
const key = `${action}-${identifier}`;
const success = checkInMemoryRateLimit(key, config.requests, windowMs);
const info = getInMemoryRateLimitInfo(key, config.requests);
return {
success,
remaining: info.remaining,
reset: new Date(info.resetTime).getTime(),
limit: info.limit };
}
/**
* Create rate limit response headers
*/
export function createRateLimitHeaders(result: RateLimitResult): Record<string, string> {
return {
"X-RateLimit-Limit": result.limit.toString(),
"X-RateLimit-Remaining": result.remaining.toString(),
"X-RateLimit-Reset": new Date(result.reset).toISOString() };
}
/**
* Helper to check rate limit and create response if exceeded
*/
export async function withRateLimit(
action: RateLimitAction,
identifier: string
): Promise<{ allowed: boolean; headers: Record<string, string>; result: RateLimitResult }> {
const result = await checkDistributedRateLimit(action, identifier);
const headers = createRateLimitHeaders(result);
return {
allowed: result.success,
headers,
result };
}
|