IC-Webhosting: Domaenen, Webseiten und Postfaecher ueber Anmeldungen
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.
This commit is contained in:
commit
a2ce3a69fd
14 changed files with 3018 additions and 0 deletions
329
server/manage.lua
Normal file
329
server/manage.lua
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- ic-web – Verwaltung
|
||||
--
|
||||
-- Zwei Ebenen, beide ueber die Anmeldung:
|
||||
--
|
||||
-- superadmin legt Domaenen an, setzt je Domaene einen Admin ein, sperrt
|
||||
-- und loescht. Sieht alles.
|
||||
-- admin verwaltet die Zugaenge seiner Domaene und pflegt ihre Seiten.
|
||||
--
|
||||
-- Ein Zugang ist zugleich ein Postfach: wer hier einen Zugang anlegt, legt
|
||||
-- damit die Mailadresse mit an.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
ICWeb = ICWeb or {}
|
||||
ICWeb.Manage = {}
|
||||
|
||||
local M = ICWeb.Manage
|
||||
local DB = ICWeb.DB
|
||||
local FW = ICWeb.FW
|
||||
local D = ICWeb.Domains
|
||||
local A = ICWeb.Auth
|
||||
|
||||
-- Was der Client ueber einen Zugang erfahren darf.
|
||||
local function publicAccount(row)
|
||||
return {
|
||||
id = row.id,
|
||||
username = row.username,
|
||||
domain = row.domain,
|
||||
role = row.role,
|
||||
display_name = row.display_name,
|
||||
active = ICWeb.Bool(row.active),
|
||||
must_change = ICWeb.Bool(row.must_change),
|
||||
last_login = row.last_login,
|
||||
created_at = row.created_at,
|
||||
}
|
||||
end
|
||||
|
||||
-- ── Zugaenge einer Domaene ──────────────────────────────────────────────────
|
||||
function M.ListAccounts(src, domain)
|
||||
domain = tostring(domain or ''):lower()
|
||||
if not A.CanManageAccounts(src, domain) then return nil, 'Keine Berechtigung.' end
|
||||
|
||||
local out = {}
|
||||
for _, row in ipairs(DB.GetAccountsForDomain(domain)) do
|
||||
out[#out + 1] = publicAccount(row)
|
||||
end
|
||||
return out, nil
|
||||
end
|
||||
|
||||
-- Gibt (zugang, nil) oder (nil, fehlertext) zurueck.
|
||||
--
|
||||
-- Das Passwort wird vom Anleger vergeben und danach weitergegeben. Es kommt
|
||||
-- deshalb einmal im Klartext zurueck an den Bildschirm des Anlegers, aber nie
|
||||
-- wieder aus der Datenbank heraus.
|
||||
function M.CreateAccount(src, domain, username, password, role, displayName)
|
||||
domain = tostring(domain or ''):lower()
|
||||
if not A.CanManageAccounts(src, domain) then return nil, 'Keine Berechtigung.' end
|
||||
|
||||
local site = DB.GetSiteByDomain(domain)
|
||||
if not site then return nil, 'Domäne nicht gefunden.' end
|
||||
|
||||
-- Nur der Anbieter darf weitere Anbieterzugaenge oder Domaenenadmins
|
||||
-- ausserhalb der eigenen Domaene anlegen.
|
||||
if role == 'superadmin' and not A.IsSuperadmin(src) then
|
||||
return nil, 'Keine Berechtigung für diese Rolle.'
|
||||
end
|
||||
if role ~= 'admin' and role ~= 'editor' and role ~= 'superadmin' then
|
||||
return nil, 'Unbekannte Rolle.'
|
||||
end
|
||||
|
||||
if DB.CountAccountsForDomain(domain) >= (Config.MaxAccountsPerDomain or 15) then
|
||||
return nil, ('Höchstens %d Zugänge je Domäne.'):format(Config.MaxAccountsPerDomain or 15)
|
||||
end
|
||||
|
||||
local session = A.Get(src)
|
||||
local id, err = A.CreateAccount({
|
||||
username = username,
|
||||
domain = domain,
|
||||
role = role,
|
||||
display_name = displayName,
|
||||
password = password,
|
||||
must_change = true,
|
||||
created_by = session and session.username or 'system',
|
||||
})
|
||||
if not id then return nil, err end
|
||||
|
||||
ICWeb.Log.Write(src, 'account.created',
|
||||
{ domain = domain, username = tostring(username):lower(), role = role })
|
||||
|
||||
return publicAccount(DB.GetAccountById(id)), nil
|
||||
end
|
||||
|
||||
function M.SetAccountPassword(src, accountId, password)
|
||||
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
||||
if not account then return false, 'Zugang nicht gefunden.' end
|
||||
|
||||
-- Den eigenen Zugang darf jeder aendern, fremde nur die Verwaltung.
|
||||
local session = A.Get(src)
|
||||
local own = session ~= nil and session.username == account.username
|
||||
if not own and not A.CanManageAccounts(src, account.domain) then
|
||||
return false, 'Keine Berechtigung.'
|
||||
end
|
||||
-- Der Anbieterzugang laesst sich nur von ihm selbst oder einem anderen
|
||||
-- Anbieterzugang aendern.
|
||||
if account.role == 'superadmin' and not own and not A.IsSuperadmin(src) then
|
||||
return false, 'Keine Berechtigung.'
|
||||
end
|
||||
|
||||
local ok, err = A.SetPassword(account.id, password)
|
||||
if not ok then return false, err end
|
||||
|
||||
if own then A.ClearMustChange(src) end
|
||||
|
||||
ICWeb.Log.Write(src, 'account.password.changed', { username = account.username })
|
||||
return true, nil
|
||||
end
|
||||
|
||||
function M.SetAccountActive(src, accountId, active)
|
||||
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
||||
if not account then return false, 'Zugang nicht gefunden.' end
|
||||
if not A.CanManageAccounts(src, account.domain) then return false, 'Keine Berechtigung.' end
|
||||
if account.role == 'superadmin' and not A.IsSuperadmin(src) then
|
||||
return false, 'Keine Berechtigung.'
|
||||
end
|
||||
|
||||
local session = A.Get(src)
|
||||
if session and session.username == account.username then
|
||||
return false, 'Den eigenen Zugang kannst du nicht sperren.'
|
||||
end
|
||||
|
||||
DB.SetAccountActive(account.id, active == true)
|
||||
ICWeb.Log.Write(src, active and 'account.enabled' or 'account.disabled',
|
||||
{ username = account.username })
|
||||
return true, nil
|
||||
end
|
||||
|
||||
function M.SetAccountRole(src, accountId, role)
|
||||
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
||||
if not account then return false, 'Zugang nicht gefunden.' end
|
||||
if not A.CanManageAccounts(src, account.domain) then return false, 'Keine Berechtigung.' end
|
||||
|
||||
if role ~= 'admin' and role ~= 'editor' then return false, 'Unbekannte Rolle.' end
|
||||
if account.role == 'superadmin' then return false, 'Der Anbieterzugang bleibt, wie er ist.' end
|
||||
|
||||
local session = A.Get(src)
|
||||
if session and session.username == account.username then
|
||||
return false, 'Die eigene Rolle kannst du nicht ändern.'
|
||||
end
|
||||
|
||||
DB.SetAccountRole(account.id, role)
|
||||
ICWeb.Log.Write(src, 'account.role.changed', { username = account.username, role = role })
|
||||
return true, nil
|
||||
end
|
||||
|
||||
-- Loeschen entfernt den Zugang, nicht das Postfach: dort liegen Nachrichten.
|
||||
function M.DeleteAccount(src, accountId)
|
||||
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
||||
if not account then return false, 'Zugang nicht gefunden.' end
|
||||
if not A.CanManageAccounts(src, account.domain) then return false, 'Keine Berechtigung.' end
|
||||
if account.role == 'superadmin' then return false, 'Der Anbieterzugang lässt sich nicht löschen.' end
|
||||
|
||||
local session = A.Get(src)
|
||||
if session and session.username == account.username then
|
||||
return false, 'Den eigenen Zugang kannst du nicht löschen.'
|
||||
end
|
||||
|
||||
DB.DeleteAccount(account.id)
|
||||
ICWeb.Log.Write(src, 'account.deleted', { username = account.username }, 'warn')
|
||||
return true, nil
|
||||
end
|
||||
|
||||
-- ── Anbieterebene ───────────────────────────────────────────────────────────
|
||||
function M.AdminListSites(src)
|
||||
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
||||
|
||||
local out = {}
|
||||
for _, s in ipairs(DB.GetAllSites()) do
|
||||
out[#out + 1] = {
|
||||
domain = s.domain,
|
||||
title = s.title,
|
||||
owner_type = s.owner_type,
|
||||
owner_id = s.owner_id,
|
||||
published = ICWeb.Bool(s.published),
|
||||
blocked = ICWeb.Bool(s.blocked),
|
||||
pages = DB.CountPages(s.id),
|
||||
accounts = DB.CountAccountsForDomain(s.domain),
|
||||
created_at = s.created_at,
|
||||
}
|
||||
end
|
||||
return out, nil
|
||||
end
|
||||
|
||||
-- Domaene anlegen und gleich den ersten Zugang dafuer einrichten. Ohne diesen
|
||||
-- Zugang koennte niemand die neue Seite pflegen.
|
||||
--
|
||||
-- Gibt (site, nil, zugangsdaten) oder (nil, fehlertext) zurueck.
|
||||
function M.AdminRegister(src, domain, title, ownerId, adminUser, adminPassword)
|
||||
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
||||
|
||||
-- Der Anbieter darf auch Behoerdennamen vergeben – dafuer ist er da.
|
||||
local name, err = D.CheckName(domain, true)
|
||||
if not name then return nil, err end
|
||||
|
||||
ownerId = tostring(ownerId or ''):lower()
|
||||
if ownerId == '' then return nil, 'Kein Besitzer (Job) angegeben.' end
|
||||
|
||||
if DB.GetSiteByDomain(name) then return nil, 'Diese Domäne ist bereits vergeben.' end
|
||||
|
||||
-- Zugangsdaten zuerst pruefen: eine Domaene ohne Zugang waere unbrauchbar,
|
||||
-- und ein Rueckbau nach dem Anlegen ist fehleranfaelliger als eine
|
||||
-- Pruefung davor.
|
||||
local user = tostring(adminUser or 'admin'):lower():gsub('[^a-z0-9%.%-_]', '')
|
||||
if user == '' then user = 'admin' end
|
||||
local username = user .. '@' .. name
|
||||
|
||||
local password = adminPassword
|
||||
local generated = false
|
||||
if type(password) ~= 'string' or password == '' then
|
||||
password = D.RandomPassword()
|
||||
generated = true
|
||||
end
|
||||
|
||||
local pwErr = A.CheckPassword(password)
|
||||
if pwErr then return nil, pwErr end
|
||||
|
||||
local realmOk, realmErr = D.EnsureRealm(name, title, ownerId)
|
||||
if not realmOk then return nil, realmErr end
|
||||
|
||||
local site = D.CreateSiteWithHome(name, title, 'job', ownerId,
|
||||
(A.Get(src) or {}).username or 'system')
|
||||
if not site then return nil, 'Speichern fehlgeschlagen.' end
|
||||
|
||||
local accountId, accErr = A.CreateAccount({
|
||||
username = username,
|
||||
domain = name,
|
||||
role = 'admin',
|
||||
display_name = title or name,
|
||||
password = password,
|
||||
must_change = true,
|
||||
created_by = (A.Get(src) or {}).username or 'system',
|
||||
})
|
||||
if not accountId then
|
||||
-- Ohne Zugang ist die Domaene nicht pflegbar; lieber nichts stehen lassen.
|
||||
DB.DeleteSite(site.id)
|
||||
return nil, accErr
|
||||
end
|
||||
|
||||
ICWeb.Log.Write(src, 'domain.registered', { domain = name, owner = ownerId }, 'warn')
|
||||
|
||||
return site, nil, { username = username, password = password, generated = generated }
|
||||
end
|
||||
|
||||
function M.AdminSetBlocked(src, domain, blocked)
|
||||
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
||||
|
||||
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
||||
if not site then return false, 'Domäne nicht gefunden.' end
|
||||
|
||||
DB.SetBlocked(site.id, blocked == true)
|
||||
ICWeb.Log.Write(src, blocked and 'site.blocked' or 'site.unblocked',
|
||||
{ domain = site.domain }, 'warn')
|
||||
return true, nil
|
||||
end
|
||||
|
||||
-- Besitzer wechseln, etwa wenn eine Firma verkauft wird.
|
||||
function M.AdminSetOwner(src, domain, ownerId)
|
||||
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
||||
|
||||
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
||||
if not site then return false, 'Domäne nicht gefunden.' end
|
||||
|
||||
ownerId = tostring(ownerId or ''):lower()
|
||||
if ownerId == '' then return false, 'Kein Besitzer angegeben.' end
|
||||
|
||||
DB.SetOwner(site.id, 'job', ownerId)
|
||||
ICWeb.Log.Write(src, 'site.owner.changed',
|
||||
{ domain = site.domain, from = site.owner_id, to = ownerId }, 'warn')
|
||||
return true, nil
|
||||
end
|
||||
|
||||
-- Loeschen entfernt Seiten und Meldungen ueber ON DELETE CASCADE sowie die
|
||||
-- Zugaenge dieser Domaene. Die Mail-Domaene und die Postfaecher bleiben
|
||||
-- bestehen: dort liegen Nachrichten, die eine geloeschte Webseite nichts angehen.
|
||||
function M.AdminDeleteSite(src, domain)
|
||||
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
||||
|
||||
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
||||
if not site then return false, 'Domäne nicht gefunden.' end
|
||||
|
||||
for _, acc in ipairs(DB.GetAccountsForDomain(site.domain)) do
|
||||
DB.DeleteAccount(acc.id)
|
||||
end
|
||||
DB.DeleteSite(site.id)
|
||||
|
||||
ICWeb.Log.Write(src, 'site.deleted', { domain = site.domain }, 'warn')
|
||||
return true, nil
|
||||
end
|
||||
|
||||
function M.AdminListReports(src)
|
||||
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
||||
return DB.GetOpenReports(), nil
|
||||
end
|
||||
|
||||
function M.AdminSetReportStatus(src, id, status)
|
||||
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
||||
if status ~= 'reviewed' and status ~= 'dismissed' and status ~= 'open' then
|
||||
return false, 'Unbekannter Status.'
|
||||
end
|
||||
|
||||
DB.SetReportStatus(tonumber(id) or -1, status)
|
||||
return true, nil
|
||||
end
|
||||
|
||||
-- Jobliste fuer das Besitzerfeld in der Verwaltung.
|
||||
function M.AdminListJobs(src)
|
||||
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
||||
return FW.GetJobs(), nil
|
||||
end
|
||||
|
||||
-- Alle Zugaenge quer ueber alle Domaenen.
|
||||
function M.AdminListAccounts(src)
|
||||
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
||||
|
||||
local out = {}
|
||||
for _, row in ipairs(DB.GetAllAccounts()) do
|
||||
out[#out + 1] = publicAccount(row)
|
||||
end
|
||||
return out, nil
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue