- Add comprehensive API-key authentication system with X-API-Key header validation - Implement permission-based access control (mail:send, * for admin) - Add rate-limiting system (60 req/hour per API key, 100 req/hour per IP) - Refactor monolithic 590-line main.lua into 6 modular components (<200 lines each) - Add IP-restriction support with CIDR notation (127.0.0.1, 10.0.0.0/8) - Implement Hugo integration with CORS support for localhost:1313 - Add production-ready configuration with environment variable support - Create comprehensive testing suite (auth, rate-limiting, stress tests) - Add production deployment checklist and cleanup scripts This refactoring transforms the API gateway from a single-file monolith into a biocodie-compliant modular architecture while adding enterprise-grade security features. Performance testing shows 79 RPS concurrent throughput with <100ms latency. Hugo contact form integration tested and working. System is now production-ready for deployment to walter/aitvaras. Resolves #47
139 lines
4.3 KiB
Lua
139 lines
4.3 KiB
Lua
-- furt-lua/src/auth.lua
|
|
-- API Key authentication system
|
|
-- Dragons@Work Digital Sovereignty Project
|
|
|
|
local IpUtils = require("src.ip_utils")
|
|
local RateLimiter = require("src.rate_limiter")
|
|
|
|
local Auth = {}
|
|
|
|
-- Load configuration
|
|
local config = require("config.server")
|
|
|
|
-- Authenticate incoming request
|
|
function Auth.authenticate_request(request)
|
|
local api_key = request.headers["x-api-key"]
|
|
|
|
if not api_key then
|
|
return false, "Missing X-API-Key header", 401
|
|
end
|
|
|
|
-- Check if API key exists in config
|
|
local key_config = config.api_keys and config.api_keys[api_key]
|
|
if not key_config then
|
|
return false, "Invalid API key", 401
|
|
end
|
|
|
|
-- Get client IP
|
|
local client_ip = IpUtils.get_client_ip(request)
|
|
|
|
-- Check IP restrictions
|
|
if not IpUtils.is_ip_allowed(client_ip, key_config.allowed_ips) then
|
|
return false, "IP address not allowed", 403
|
|
end
|
|
|
|
-- Check rate limits
|
|
local rate_ok, rate_message, rate_info = RateLimiter:check_api_and_ip_limits(api_key, client_ip)
|
|
if not rate_ok then
|
|
return false, rate_message, 429, rate_info
|
|
end
|
|
|
|
-- Return auth context
|
|
return true, {
|
|
api_key = api_key,
|
|
key_name = key_config.name,
|
|
permissions = key_config.permissions or {},
|
|
client_ip = client_ip,
|
|
rate_info = rate_info
|
|
}
|
|
end
|
|
|
|
-- Check if user has specific permission
|
|
function Auth.has_permission(auth_context, required_permission)
|
|
if not auth_context or not auth_context.permissions then
|
|
return false
|
|
end
|
|
|
|
-- No permission required = always allow for authenticated users
|
|
if not required_permission then
|
|
return true
|
|
end
|
|
|
|
-- Check for specific permission or wildcard
|
|
for _, permission in ipairs(auth_context.permissions) do
|
|
if permission == required_permission or permission == "*" then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Create auth middleware wrapper for route handlers
|
|
function Auth.create_protected_route(required_permission, handler)
|
|
return function(request, server)
|
|
-- Authenticate request
|
|
local auth_success, auth_result, status_code, rate_info = Auth.authenticate_request(request)
|
|
|
|
if not auth_success then
|
|
local error_response = {
|
|
error = auth_result,
|
|
timestamp = os.time()
|
|
}
|
|
|
|
-- Add rate limit info to error if available
|
|
if rate_info then
|
|
error_response.rate_limit = rate_info
|
|
end
|
|
|
|
return server:create_response(status_code or 401, error_response, nil, nil, request)
|
|
end
|
|
|
|
-- Check permissions
|
|
if required_permission and not Auth.has_permission(auth_result, required_permission) then
|
|
return server:create_response(403, {
|
|
error = "Insufficient permissions",
|
|
required = required_permission,
|
|
available = auth_result.permissions
|
|
}, nil, nil, request)
|
|
end
|
|
|
|
-- Add auth context to request
|
|
request.auth = auth_result
|
|
|
|
-- Get rate limit headers
|
|
local rate_headers = RateLimiter:get_rate_limit_headers(auth_result.rate_info)
|
|
|
|
-- Call original handler
|
|
local result = handler(request, server)
|
|
|
|
-- If result is a string (already formatted response), return as-is
|
|
if type(result) == "string" then
|
|
return result
|
|
end
|
|
|
|
-- If handler returned data, create response with rate limit headers
|
|
return server:create_response(200, result, "application/json", rate_headers, request)
|
|
end
|
|
end
|
|
|
|
-- Get authentication status for debug/monitoring
|
|
function Auth.get_auth_status(auth_context)
|
|
if not auth_context then
|
|
return {
|
|
authenticated = false
|
|
}
|
|
end
|
|
|
|
return {
|
|
authenticated = true,
|
|
api_key_name = auth_context.key_name,
|
|
permissions = auth_context.permissions,
|
|
client_ip = auth_context.client_ip,
|
|
rate_limit_remaining = auth_context.rate_info and auth_context.rate_info.api_key and auth_context.rate_info.api_key.remaining,
|
|
ip_rate_limit_remaining = auth_context.rate_info and auth_context.rate_info.ip and auth_context.rate_info.ip.remaining
|
|
}
|
|
end
|
|
|
|
return Auth
|
|
|