You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

89 lines
2.1 KiB

// server/middleware/rate-limit.ts
/**
* Rate limiting middleware for auth endpoints
*
* Prevents brute force attacks on login/registration
*
* Limits:
* - /api/auth/login: 5 attempts per 15 minutes per IP
* - /api/auth/register: 3 attempts per hour per IP
*/
interface RateLimitEntry {
count: number
resetAt: number
}
// In-memory rate limit store (use Redis in production!)
const rateLimitStore = new Map<string, RateLimitEntry>()
// Clean up expired entries every 5 minutes
setInterval(
() => {
const now = Date.now()
for (const [key, entry] of rateLimitStore.entries()) {
if (entry.resetAt < now) {
rateLimitStore.delete(key)
}
}
},
5 * 60 * 1000
)
export default defineEventHandler((event) => {
const path = event.path
// Only apply to auth endpoints
if (!path.startsWith('/api/auth/')) {
return
}
// Get client IP
const ip = getRequestIP(event, { xForwardedFor: true }) || 'unknown'
// Define rate limits per endpoint
const limits: Record<string, { maxAttempts: number; windowMs: number }> = {
'/api/auth/login': { maxAttempts: 10, windowMs: 10 * 60 * 1000 }, // 10 per 10min
'/api/auth/register': { maxAttempts: 3, windowMs: 60 * 60 * 1000 }, // 3 per hour
}
const limit = limits[path]
if (!limit) {
return // No rate limit for this endpoint
}
// Check rate limit
const key = `${ip}:${path}`
const now = Date.now()
const entry = rateLimitStore.get(key)
if (!entry || entry.resetAt < now) {
// First attempt or window expired - reset counter
rateLimitStore.set(key, {
count: 1,
resetAt: now + limit.windowMs,
})
return
}
// Increment counter
entry.count++
if (entry.count > limit.maxAttempts) {
// Rate limit exceeded
const retryAfter = Math.ceil((entry.resetAt - now) / 1000)
setResponseStatus(event, 429)
setResponseHeader(event, 'Retry-After', retryAfter.toString())
throw createError({
statusCode: 429,
statusMessage: 'Too many requests',
data: {
retryAfter,
message: `Too many attempts. Please try again in ${retryAfter} seconds.`,
},
})
}
})