finlion-website/src/lib/rate-limit.ts

88 lines
2.4 KiB
TypeScript

import { Redis } from '@upstash/redis'
import { headers } from 'next/headers'
export interface RateLimitConfig {
interval: number // in seconds
limit: number
}
export interface RateLimitResult {
success: boolean
limit: number
remaining: number
reset: number // timestamp in seconds
}
export class RateLimit {
private redis: Redis
private prefix: string
constructor() {
this.redis = Redis.fromEnv()
this.prefix = 'ratelimit'
}
private async getIP(): Promise<string> {
try {
const headersList = await headers()
const forwardedFor = headersList.get('x-forwarded-for')
const realIP = headersList.get('x-real-ip')
return forwardedFor?.split(',')[0] || realIP || 'development'
} catch (error) {
console.warn('Failed to get IP address, using fallback')
return 'development'
}
}
private getKey(identifier: string): string {
return `${this.prefix}:${identifier}`
}
async check(config: RateLimitConfig): Promise<RateLimitResult> {
try {
const ip = await this.getIP()
const key = this.getKey(ip)
const now = Math.floor(Date.now() / 1000)
const windowStart = now - (now % config.interval)
const windowKey = `${key}:${windowStart}`
const pipeline = this.redis.pipeline()
pipeline.incr(windowKey)
pipeline.expire(windowKey, config.interval)
const [count] = await pipeline.exec()
const remaining = Math.max(0, config.limit - (count as number))
return {
success: (count as number) <= config.limit,
limit: config.limit,
remaining,
reset: windowStart + config.interval
}
} catch (error) {
console.warn('Redis unavailable, allowing request (rate limiting disabled):', error)
// Fallback: allow the request when Redis is unavailable
// TODO: Fix Redis connection or create new instance
return {
success: true,
limit: config.limit,
remaining: config.limit - 1,
reset: Math.floor(Date.now() / 1000) + config.interval
}
}
}
}
// Default configuration
const DEFAULT_CONFIG: RateLimitConfig = {
interval: 60 * 60, // 1 hour
limit: 5 // 5 requests per hour
}
// Helper function for easy usage
export async function rateLimit(
config: RateLimitConfig = DEFAULT_CONFIG
): Promise<RateLimitResult> {
const limiter = new RateLimit()
return limiter.check(config)
}