140 lines
4.3 KiB
Lua
140 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
|
||
|
|
|