Ein IC-Hostinganbieter im Spiel. Er vergibt Domaenen, richtet je Domaene einen Zugang ein, und die Inhaber pflegen darauf ihre Webseite. Die gesamte Verwaltung haengt an einer Anmeldung, nicht an Job oder Serverrechten. Ein Zugang ist zugleich ein Postfach: wer sich anmeldet, bekommt das Postfach dieser Adresse. Mehrere Personen koennen denselben Zugang benutzen und dasselbe Postfach gleichzeitig offen haben - ein Firmenpostfach gehoert der Firma, nicht einer Person. Drei Ebenen: superadmin vergibt Domaenen und setzt je Domaene einen Admin ein admin pflegt die Seiten seiner Domaene und legt dort Zugaenge an editor pflegt nur die Seiten Seiteninhalte sind kein HTML, sondern typisierte Bloecke. Die Seiten werden im NUI des IC-Computers angezeigt, also im selben Kontext wie dieser selbst - wer dort Markup einschleusen kann, kann auch dessen Callbacks aufrufen. Ein Blockmodell schliesst das konstruktiv aus, statt es filtern zu muessen. Passwoerter liegen gesalzen und gehasht in der Datenbank, gerechnet wird in der Datenbank. Fuenf Fehlversuche je Benutzername sperren den Namen kurzzeitig. Enthaelt README.md mit Einrichtung und Anbindung an den IC-Computer sowie ANLEITUNG.md fuer die Bedienung im Spiel.
137 lines
5 KiB
Lua
137 lines
5 KiB
Lua
-- ─────────────────────────────────────────────────────────────────────────────
|
||
-- ic-web – ESX-Bridge, Rechte und Logging
|
||
--
|
||
-- Alle Framework-Zugriffe zentral. ESX wird bei Bedarf geholt, nicht beim
|
||
-- Laden: beim Start ist es je nach Reihenfolge noch nicht bereit, und ein
|
||
-- einmal fehlgeschlagener Versuch haette die Bridge dauerhaft lahmgelegt.
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
ICWeb = ICWeb or {}
|
||
ICWeb.FW = {}
|
||
local FW = ICWeb.FW
|
||
|
||
local ESX
|
||
|
||
local function esx()
|
||
if ESX then return ESX end
|
||
local ok, obj = pcall(function() return exports['es_extended']:getSharedObject() end)
|
||
if ok and obj then ESX = obj end
|
||
return ESX
|
||
end
|
||
|
||
local function player(src)
|
||
local api = esx()
|
||
return api and api.GetPlayerFromId(src) or nil
|
||
end
|
||
|
||
function FW.GetIdentifier(src)
|
||
local xPlayer = player(src)
|
||
return xPlayer and xPlayer.identifier or nil
|
||
end
|
||
|
||
function FW.GetName(src)
|
||
local xPlayer = player(src)
|
||
if not xPlayer then return GetPlayerName(src) or 'Unbekannt' end
|
||
if xPlayer.getName then return xPlayer.getName() end
|
||
return xPlayer.name or 'Unbekannt'
|
||
end
|
||
|
||
-- Job und Dienstgrad. Beides wird fuer die Registrierung gebraucht.
|
||
function FW.GetJob(src)
|
||
local xPlayer = player(src)
|
||
if not xPlayer or not xPlayer.job then return nil, 0 end
|
||
return xPlayer.job.name, tonumber(xPlayer.job.grade) or 0
|
||
end
|
||
|
||
-- Jobliste fuer die Adminansicht. Der Job entscheidet nicht mehr ueber Rechte,
|
||
-- er steht nur noch als Besitzervermerk an einer Domaene.
|
||
function FW.GetJobs()
|
||
local api = esx()
|
||
if not api or not api.GetJobs then return {} end
|
||
|
||
local out = {}
|
||
for name, job in pairs(api.GetJobs()) do
|
||
out[#out + 1] = { name = name, label = job.label or name }
|
||
end
|
||
table.sort(out, function(a, b) return a.label < b.label end)
|
||
return out
|
||
end
|
||
|
||
function FW.Notify(src, message)
|
||
local xPlayer = player(src)
|
||
if xPlayer and xPlayer.showNotification then
|
||
xPlayer.showNotification(message)
|
||
return
|
||
end
|
||
TriggerClientEvent('esx:showNotification', src, message)
|
||
end
|
||
|
||
-- ── Adminrechte ─────────────────────────────────────────────────────────────
|
||
function FW.IsAdmin(src)
|
||
if not src or src == 0 then return true end -- Serverkonsole
|
||
|
||
local mode = Config.Admin.mode or 'either'
|
||
|
||
local okEsx = false
|
||
local xPlayer = player(src)
|
||
if xPlayer and xPlayer.getGroup then
|
||
okEsx = Config.Admin.esxGroups[xPlayer.getGroup()] == true
|
||
end
|
||
|
||
local okAce = IsPlayerAceAllowed(src, Config.Admin.acePermission) == true
|
||
|
||
if mode == 'esx' then return okEsx end
|
||
if mode == 'ace' then return okAce end
|
||
if mode == 'both' then return okEsx and okAce end
|
||
return okEsx or okAce
|
||
end
|
||
|
||
-- ── Logging ─────────────────────────────────────────────────────────────────
|
||
-- Ausschliesslich ueber fmsdk. Fehlt es, wird verworfen statt zu crashen.
|
||
ICWeb.Log = {}
|
||
|
||
function ICWeb.Log.Write(src, action, details, level)
|
||
if GetResourceState('fmsdk') ~= 'started' then
|
||
if Config.Debug then
|
||
print(('[ic-web] fmsdk fehlt, Log verworfen: %s'):format(tostring(action)))
|
||
end
|
||
return false
|
||
end
|
||
|
||
local meta = details or {}
|
||
meta.action = (Config.Logging.actionPrefix or 'ic_web.') .. tostring(action)
|
||
meta.resource = GetCurrentResourceName()
|
||
meta.playerSource = src or nil
|
||
meta.identifier = src and FW.GetIdentifier(src) or nil
|
||
|
||
local ok = pcall(function()
|
||
exports.fmsdk:Log(Config.Logging.dataset or 'ic_web', level or 'info',
|
||
('Webhosting: %s'):format(meta.action), meta)
|
||
end)
|
||
return ok
|
||
end
|
||
|
||
-- ── Drossel ─────────────────────────────────────────────────────────────────
|
||
-- Jedes RegisterNetEvent ist frei aufrufbar.
|
||
local buckets = {}
|
||
|
||
function ICWeb.ThrottleOk(src, name, maxPerWindow, windowMs)
|
||
local now = GetGameTimer()
|
||
local key = ('%s:%s'):format(src, name)
|
||
local entry = buckets[key]
|
||
|
||
if not entry or (now - entry.start) > windowMs then
|
||
buckets[key] = { start = now, count = 1 }
|
||
return true
|
||
end
|
||
|
||
entry.count = entry.count + 1
|
||
return entry.count <= maxPerWindow
|
||
end
|
||
|
||
AddEventHandler('playerDropped', function()
|
||
local prefix = tostring(source) .. ':'
|
||
for key in pairs(buckets) do
|
||
if key:sub(1, #prefix) == prefix then buckets[key] = nil end
|
||
end
|
||
end)
|