feat(config): integrate rate limiting and CORS configuration from furt.conf
- Add RateLimiter:configure() function to accept config-based limits - Integrate security section parameters (rate_limit_api_key_max, ip_max, window) - Add CORS configuration from config file with environment fallback - Replace hardcoded rate limiting defaults with configurable values - Add test endpoint control via config.security.enable_test_endpoint - Update startup logging to show actual configured rate limits - Add configuration validation and detailed startup information Rate limiting now uses values from [security] section instead of hardcoded defaults. CORS origins prioritize config file over environment variables. Related to DAW/furt#89
This commit is contained in:
parent
ecd4f68595
commit
5c17c86fd4
4 changed files with 128 additions and 49 deletions
|
|
@ -1,4 +1,4 @@
|
|||
-- furt-lua/src/rate_limiter.lua
|
||||
-- src/rate_limiter.lua
|
||||
-- Rate limiting system for API requests
|
||||
-- Dragons@Work Digital Sovereignty Project
|
||||
|
||||
|
|
@ -6,8 +6,8 @@ local RateLimiter = {
|
|||
requests = {}, -- {api_key = {timestamps}, ip = {timestamps}}
|
||||
cleanup_interval = 300, -- Cleanup every 5 minutes
|
||||
last_cleanup = os.time(),
|
||||
|
||||
-- Default limits
|
||||
|
||||
-- Default limits (configurable)
|
||||
default_limits = {
|
||||
api_key_max = 60, -- 60 requests per hour per API key
|
||||
ip_max = 100, -- 100 requests per hour per IP
|
||||
|
|
@ -15,15 +15,35 @@ local RateLimiter = {
|
|||
}
|
||||
}
|
||||
|
||||
-- Configure rate limits from config
|
||||
function RateLimiter:configure(limits)
|
||||
if limits then
|
||||
if limits.api_key_max then
|
||||
self.default_limits.api_key_max = limits.api_key_max
|
||||
end
|
||||
if limits.ip_max then
|
||||
self.default_limits.ip_max = limits.ip_max
|
||||
end
|
||||
if limits.window then
|
||||
self.default_limits.window = limits.window
|
||||
end
|
||||
|
||||
print("Rate limiting configured:")
|
||||
print(" API Key limit: " .. self.default_limits.api_key_max .. " req/hour")
|
||||
print(" IP limit: " .. self.default_limits.ip_max .. " req/hour")
|
||||
print(" Window: " .. self.default_limits.window .. " seconds")
|
||||
end
|
||||
end
|
||||
|
||||
-- Cleanup old requests from memory
|
||||
function RateLimiter:cleanup_old_requests()
|
||||
local now = os.time()
|
||||
if now - self.last_cleanup < self.cleanup_interval then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local cutoff = now - self.default_limits.window
|
||||
|
||||
|
||||
for key, timestamps in pairs(self.requests) do
|
||||
local filtered = {}
|
||||
for _, timestamp in ipairs(timestamps) do
|
||||
|
|
@ -33,21 +53,21 @@ function RateLimiter:cleanup_old_requests()
|
|||
end
|
||||
self.requests[key] = filtered
|
||||
end
|
||||
|
||||
|
||||
self.last_cleanup = now
|
||||
end
|
||||
|
||||
-- Check if request is within rate limit
|
||||
function RateLimiter:check_rate_limit(key, max_requests, window_seconds)
|
||||
self:cleanup_old_requests()
|
||||
|
||||
|
||||
local now = os.time()
|
||||
local cutoff = now - (window_seconds or self.default_limits.window)
|
||||
|
||||
|
||||
if not self.requests[key] then
|
||||
self.requests[key] = {}
|
||||
end
|
||||
|
||||
|
||||
-- Count requests in time window
|
||||
local count = 0
|
||||
for _, timestamp in ipairs(self.requests[key]) do
|
||||
|
|
@ -55,15 +75,15 @@ function RateLimiter:check_rate_limit(key, max_requests, window_seconds)
|
|||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Check if limit exceeded
|
||||
if count >= max_requests then
|
||||
return false, count, max_requests - count
|
||||
end
|
||||
|
||||
|
||||
-- Record this request
|
||||
table.insert(self.requests[key], now)
|
||||
|
||||
|
||||
return true, count + 1, max_requests - (count + 1)
|
||||
end
|
||||
|
||||
|
|
@ -71,11 +91,11 @@ end
|
|||
function RateLimiter:check_api_and_ip_limits(api_key, client_ip)
|
||||
-- Check API key rate limit
|
||||
local api_key_allowed, api_count, api_remaining = self:check_rate_limit(
|
||||
"api_key:" .. api_key,
|
||||
self.default_limits.api_key_max,
|
||||
"api_key:" .. api_key,
|
||||
self.default_limits.api_key_max,
|
||||
self.default_limits.window
|
||||
)
|
||||
|
||||
|
||||
if not api_key_allowed then
|
||||
return false, "API key rate limit exceeded", {
|
||||
type = "api_key",
|
||||
|
|
@ -84,14 +104,14 @@ function RateLimiter:check_api_and_ip_limits(api_key, client_ip)
|
|||
remaining = api_remaining
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
-- Check IP rate limit
|
||||
local ip_allowed, ip_count, ip_remaining = self:check_rate_limit(
|
||||
"ip:" .. client_ip,
|
||||
self.default_limits.ip_max,
|
||||
"ip:" .. client_ip,
|
||||
self.default_limits.ip_max,
|
||||
self.default_limits.window
|
||||
)
|
||||
|
||||
|
||||
if not ip_allowed then
|
||||
return false, "IP rate limit exceeded", {
|
||||
type = "ip",
|
||||
|
|
@ -100,7 +120,7 @@ function RateLimiter:check_api_and_ip_limits(api_key, client_ip)
|
|||
remaining = ip_remaining
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
-- Both limits OK
|
||||
return true, "OK", {
|
||||
api_key = {
|
||||
|
|
@ -121,7 +141,7 @@ function RateLimiter:get_rate_limit_headers(limit_info)
|
|||
if not limit_info or not limit_info.api_key then
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
return {
|
||||
["X-RateLimit-Remaining"] = tostring(limit_info.api_key.remaining or 0),
|
||||
["X-RateLimit-Limit"] = tostring(self.default_limits.api_key_max),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue