fix(config): lua 5.1 compatibility and multi-tenant validation
- Replace goto statements with if-not pattern for Lua 5.1 compatibility - Validate mail config only for API keys with mail:send permissions - Safe display of API key info for monitoring keys without mail config - Fix health check SMTP detection for new config structure - Multi-tenant system tested and working on port 7811 Fixes multi-tenant config parsing, validation, and health checks. Related to DAW/furt#89
This commit is contained in:
parent
a5db9a633f
commit
8ec401930c
4 changed files with 75 additions and 49 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -56,3 +56,4 @@ debug.log
|
||||||
config.local.lua
|
config.local.lua
|
||||||
config.production.lua
|
config.production.lua
|
||||||
|
|
||||||
|
config/furt.conf
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,28 @@ print(" Default SMTP: " .. (config.smtp_default.host or "not configured"))
|
||||||
local api_key_count = 0
|
local api_key_count = 0
|
||||||
for key_name, key_config in pairs(config.api_keys) do
|
for key_name, key_config in pairs(config.api_keys) do
|
||||||
api_key_count = api_key_count + 1
|
api_key_count = api_key_count + 1
|
||||||
|
|
||||||
|
-- Check if this API key has mail permissions
|
||||||
|
local has_mail_permission = false
|
||||||
|
if key_config.permissions then
|
||||||
|
for _, perm in ipairs(key_config.permissions) do
|
||||||
|
if perm == "mail:send" or perm == "*" then
|
||||||
|
has_mail_permission = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local smtp_info = ""
|
local smtp_info = ""
|
||||||
if key_config.mail_smtp_host then
|
if key_config.mail_smtp_host then
|
||||||
smtp_info = " (custom SMTP: " .. key_config.mail_smtp_host .. ")"
|
smtp_info = " (custom SMTP: " .. key_config.mail_smtp_host .. ")"
|
||||||
end
|
end
|
||||||
print(" API Key: " .. key_config.name .. " -> " .. key_config.mail_to .. smtp_info)
|
|
||||||
|
if has_mail_permission then
|
||||||
|
print(" API Key: " .. key_config.name .. " -> " .. key_config.mail_to .. smtp_info)
|
||||||
|
else
|
||||||
|
print(" API Key: " .. key_config.name .. " (no mail)" .. smtp_info)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
print(" Total API Keys: " .. api_key_count)
|
print(" Total API Keys: " .. api_key_count)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
-- src/config_parser.lua
|
-- src/config_parser.lua
|
||||||
-- nginx-style configuration parser for Multi-Tenant setup
|
-- nginx-style configuration parser for Multi-Tenant setup
|
||||||
-- Dragons@Work Digital Sovereignty Project
|
-- Dragons@Work Digital Sovereignty Project
|
||||||
|
-- Lua 5.1 compatible (no goto statements)
|
||||||
|
|
||||||
local ConfigParser = {}
|
local ConfigParser = {}
|
||||||
|
|
||||||
|
|
@ -26,53 +27,48 @@ function ConfigParser.parse_file(config_path)
|
||||||
|
|
||||||
-- Skip empty lines and comments
|
-- Skip empty lines and comments
|
||||||
line = line:match("^%s*(.-)%s*$") -- trim whitespace
|
line = line:match("^%s*(.-)%s*$") -- trim whitespace
|
||||||
if line == "" or line:match("^#") then
|
if not (line == "" or line:match("^#")) then
|
||||||
goto continue
|
-- Section headers: [section] or [api_key "keyname"]
|
||||||
end
|
local section_match = line:match("^%[([^%]]+)%]$")
|
||||||
|
if section_match then
|
||||||
-- Section headers: [section] or [api_key "keyname"]
|
if section_match:match("^api_key") then
|
||||||
local section_match = line:match("^%[([^%]]+)%]$")
|
-- Extract API key from [api_key "keyname"]
|
||||||
if section_match then
|
local key_name = section_match:match('^api_key%s+"([^"]+)"$')
|
||||||
if section_match:match("^api_key") then
|
if not key_name then
|
||||||
-- Extract API key from [api_key "keyname"]
|
error(string.format("Invalid api_key section at line %d: %s", line_number, line))
|
||||||
local key_name = section_match:match('^api_key%s+"([^"]+)"$')
|
end
|
||||||
if not key_name then
|
current_api_key = key_name
|
||||||
error(string.format("Invalid api_key section at line %d: %s", line_number, line))
|
current_section = "api_key"
|
||||||
|
config.api_keys[key_name] = {}
|
||||||
|
else
|
||||||
|
current_section = section_match
|
||||||
|
current_api_key = nil
|
||||||
|
if not config[current_section] then
|
||||||
|
config[current_section] = {}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
current_api_key = key_name
|
|
||||||
current_section = "api_key"
|
|
||||||
config.api_keys[key_name] = {}
|
|
||||||
else
|
else
|
||||||
current_section = section_match
|
-- Key-value pairs: key = value
|
||||||
current_api_key = nil
|
local key, value = line:match("^([^=]+)=(.+)$")
|
||||||
if not config[current_section] then
|
if key and value then
|
||||||
config[current_section] = {}
|
key = key:match("^%s*(.-)%s*$") -- trim
|
||||||
|
value = value:match("^%s*(.-)%s*$") -- trim
|
||||||
|
|
||||||
|
-- Remove quotes from value if present
|
||||||
|
value = value:match('^"(.*)"$') or value
|
||||||
|
|
||||||
|
if current_section == "api_key" and current_api_key then
|
||||||
|
ConfigParser.set_api_key_value(config.api_keys[current_api_key], key, value)
|
||||||
|
elseif current_section then
|
||||||
|
ConfigParser.set_config_value(config[current_section], key, value)
|
||||||
|
else
|
||||||
|
error(string.format("Key-value pair outside section at line %d: %s", line_number, line))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error(string.format("Invalid line format at line %d: %s", line_number, line))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
goto continue
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Key-value pairs: key = value
|
|
||||||
local key, value = line:match("^([^=]+)=(.+)$")
|
|
||||||
if key and value then
|
|
||||||
key = key:match("^%s*(.-)%s*$") -- trim
|
|
||||||
value = value:match("^%s*(.-)%s*$") -- trim
|
|
||||||
|
|
||||||
-- Remove quotes from value if present
|
|
||||||
value = value:match('^"(.*)"$') or value
|
|
||||||
|
|
||||||
if current_section == "api_key" and current_api_key then
|
|
||||||
ConfigParser.set_api_key_value(config.api_keys[current_api_key], key, value)
|
|
||||||
elseif current_section then
|
|
||||||
ConfigParser.set_config_value(config[current_section], key, value)
|
|
||||||
else
|
|
||||||
error(string.format("Key-value pair outside section at line %d: %s", line_number, line))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error(string.format("Invalid line format at line %d: %s", line_number, line))
|
|
||||||
end
|
|
||||||
|
|
||||||
::continue::
|
|
||||||
end
|
end
|
||||||
|
|
||||||
file:close()
|
file:close()
|
||||||
|
|
@ -163,13 +159,25 @@ function ConfigParser.validate_config(config)
|
||||||
key_config.allowed_ips = {} -- no IP restrictions
|
key_config.allowed_ips = {} -- no IP restrictions
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Validate mail configuration
|
-- Validate mail configuration only if API key has mail:send permission
|
||||||
if not key_config.mail_to then
|
local has_mail_permission = false
|
||||||
error("API key '" .. key_name .. "' missing mail_to")
|
if key_config.permissions then
|
||||||
|
for _, perm in ipairs(key_config.permissions) do
|
||||||
|
if perm == "mail:send" or perm == "*" then
|
||||||
|
has_mail_permission = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not key_config.mail_from then
|
if has_mail_permission then
|
||||||
error("API key '" .. key_name .. "' missing mail_from")
|
if not key_config.mail_to then
|
||||||
|
error("API key '" .. key_name .. "' missing mail_to")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not key_config.mail_from then
|
||||||
|
error("API key '" .. key_name .. "' missing mail_from")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ server:add_route("GET", "/health", function(request, server)
|
||||||
timestamp = os.time(),
|
timestamp = os.time(),
|
||||||
source = version_info.source,
|
source = version_info.source,
|
||||||
features = {
|
features = {
|
||||||
smtp_configured = config.mail and config.mail.username ~= nil,
|
smtp_configured = config.smtp_default and config.smtp_default.host ~= nil,
|
||||||
auth_enabled = true,
|
auth_enabled = true,
|
||||||
rate_limiting = true,
|
rate_limiting = true,
|
||||||
merkwerk_integrated = version_info.source == "merkwerk"
|
merkwerk_integrated = version_info.source == "merkwerk"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue