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.
220 lines
9.3 KiB
Lua
220 lines
9.3 KiB
Lua
-- ─────────────────────────────────────────────────────────────────────────────
|
||
-- ic-web – Domaenen: Registrierung, Besitz, Bearbeitungsrecht
|
||
--
|
||
-- Domaenen vergibt der Anbieter ueber die Verwaltung (ICWeb.Manage). Die
|
||
-- Selbstanmeldung ueber den ESX-Job weiter unten ist der aeltere Weg und
|
||
-- standardmaessig ausgeschaltet – siehe Config.SelfRegistration.
|
||
--
|
||
-- Die Domaene selbst wird in ic-mail angelegt. Dadurch bekommt eine Firma mit
|
||
-- der Webseite automatisch auch die passende Mail-Domaene, und es kann nicht
|
||
-- passieren, dass zwei Systeme dieselbe Domaene verschiedenen Besitzern geben.
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
ICWeb = ICWeb or {}
|
||
ICWeb.Domains = {}
|
||
|
||
local D = ICWeb.Domains
|
||
local DB = ICWeb.DB
|
||
local FW = ICWeb.FW
|
||
|
||
-- ── Namenspruefung ──────────────────────────────────────────────────────────
|
||
-- Gibt (domain, nil) oder (nil, fehlertext) zurueck.
|
||
--
|
||
-- allowReserved hebt nur die Namensliste auf, nicht die Formregeln. Sie soll
|
||
-- verhindern, dass Spieler sich Behoerdennamen greifen; der Anbieter vergibt
|
||
-- diese Namen ja gerade absichtlich. Der technische Schutz – Domaenen des
|
||
-- Mailsystems – haengt nicht hier, sondern in D.EnsureRealm und gilt fuer alle.
|
||
function D.CheckName(raw, allowReserved)
|
||
if type(raw) ~= 'string' then return nil, 'kein Name angegeben' end
|
||
|
||
local domain = raw:lower():gsub('%s', '')
|
||
local name, tld = domain:match('^([a-z0-9%-]+)%.([a-z]+)$')
|
||
|
||
if not name then
|
||
return nil, 'Format: name.endung, nur Buchstaben, Ziffern und Bindestrich'
|
||
end
|
||
|
||
if #name < Config.Domain.minLength or #name > Config.Domain.maxLength then
|
||
return nil, ('Name muss %d bis %d Zeichen lang sein')
|
||
:format(Config.Domain.minLength, Config.Domain.maxLength)
|
||
end
|
||
|
||
if name:sub(1, 1) == '-' or name:sub(-1) == '-' then
|
||
return nil, 'Name darf nicht mit Bindestrich beginnen oder enden'
|
||
end
|
||
|
||
local tldOk = false
|
||
for _, allowed in ipairs(Config.Domain.allowedTlds) do
|
||
if tld == allowed then tldOk = true break end
|
||
end
|
||
if not tldOk then
|
||
return nil, ('Endung nicht erlaubt. Moeglich: %s')
|
||
:format(table.concat(Config.Domain.allowedTlds, ', '))
|
||
end
|
||
|
||
if not allowReserved then
|
||
for _, reserved in ipairs(Config.Domain.reserved) do
|
||
if name == reserved then return nil, 'dieser Name ist reserviert' end
|
||
end
|
||
end
|
||
|
||
return domain, nil
|
||
end
|
||
|
||
-- ── Darf dieser Spieler ohne Anmeldung fuer seinen Job registrieren? ─────────
|
||
-- Gibt (ownerType, ownerId, nil) oder (nil, nil, fehlertext) zurueck.
|
||
--
|
||
-- Der Normalfall ist ausgeschaltet: Domaenen vergibt der Anbieter ueber die
|
||
-- Verwaltung. Siehe Config.SelfRegistration.
|
||
function D.CanRegister(src)
|
||
if not Config.SelfRegistration.enabled then
|
||
return nil, nil, 'Domaenen vergibt die Administration von Liveinvader'
|
||
end
|
||
|
||
local job, grade = FW.GetJob(src)
|
||
if not job then return nil, nil, 'kein Job' end
|
||
|
||
if Config.Registration.blockedJobs[job] then
|
||
return nil, nil, 'mit diesem Job nicht moeglich'
|
||
end
|
||
|
||
local allowed = Config.Registration.allowedJobs
|
||
if allowed and next(allowed) ~= nil then
|
||
local found = false
|
||
for _, j in ipairs(allowed) do
|
||
if j == job then found = true break end
|
||
end
|
||
if not found then return nil, nil, 'dieser Job ist nicht freigeschaltet' end
|
||
end
|
||
|
||
if grade < (Config.Registration.minGrade or 0) then
|
||
return nil, nil, ('Dienstgrad zu niedrig (mindestens %d)')
|
||
:format(Config.Registration.minGrade or 0)
|
||
end
|
||
|
||
local count = DB.CountSitesForOwner('job', job)
|
||
if count >= (Config.Registration.maxDomainsPerJob or 1) then
|
||
return nil, nil, ('Grenze erreicht: %d Domaenen je Job'):format(count)
|
||
end
|
||
|
||
return 'job', job, nil
|
||
end
|
||
|
||
-- ── Bearbeitungsrecht ───────────────────────────────────────────────────────
|
||
-- Ausschliesslich ueber die Anmeldung: wer mit einem Zugang dieser Domaene
|
||
-- angemeldet ist, darf ihre Seiten pflegen. Job und Serverrechte spielen keine
|
||
-- Rolle mehr – wer den Zugang kennt, verwaltet die Seite, unabhaengig davon,
|
||
-- welchen Charakter er gerade spielt.
|
||
function D.CanEdit(src, site)
|
||
if not site then return false end
|
||
return ICWeb.Auth.CanEdit(src, site.domain)
|
||
end
|
||
|
||
-- ── Mail-Domaene sicherstellen ──────────────────────────────────────────────
|
||
-- Gibt (true, nil) oder (false, fehlertext) zurueck.
|
||
--
|
||
-- Laeuft ic-mail nicht, gilt das als Erfolg: die Webseite funktioniert
|
||
-- eigenstaendig, es gibt dann nur keine Mailadressen darauf.
|
||
function D.EnsureRealm(domain, title, ownerId)
|
||
if GetResourceState('ic-mail') ~= 'started' then return true, nil end
|
||
|
||
local existing = exports['ic-mail']:GetRealmByName(domain)
|
||
|
||
if existing then
|
||
-- System- und Buergerdomaenen gehoeren niemandem und haben deshalb
|
||
-- owner_id = NULL. Ohne diese Pruefung wuerde die Besitzpruefung unten
|
||
-- durchwinken, und eine Firmenwebseite haenge plötzlich an mail.ls –
|
||
-- also an der Domaene saemtlicher persoenlicher Postfaecher.
|
||
if ICWeb.Bool(existing.is_default)
|
||
or existing.type == 'system' or existing.type == 'citizen' then
|
||
return false, 'diese Domaene gehoert zum Mailsystem und ist nicht vergebbar'
|
||
end
|
||
|
||
-- Domaene gibt es schon fuer Mail. Nur uebernehmen, wenn sie demselben
|
||
-- Besitzer gehoert – sonst wuerde die Webseite die Mailadressen einer
|
||
-- fremden Firma kapern.
|
||
if existing.owner_id and existing.owner_id ~= ownerId then
|
||
return false, 'diese Domaene gehoert bereits einer anderen Stelle'
|
||
end
|
||
return true, nil
|
||
end
|
||
|
||
local ok, createErr = exports['ic-mail']:CreateRealm({
|
||
realm = domain,
|
||
type = 'company',
|
||
display_name = title or domain,
|
||
owner_id = ownerId,
|
||
})
|
||
if not ok then
|
||
return false, ('Domaene konnte nicht angelegt werden: %s'):format(tostring(createErr))
|
||
end
|
||
return true, nil
|
||
end
|
||
|
||
-- ── Registrieren ────────────────────────────────────────────────────────────
|
||
-- Gibt (site, nil) oder (nil, fehlertext) zurueck.
|
||
function D.Register(src, rawDomain, title)
|
||
local ownerType, ownerId, err = D.CanRegister(src)
|
||
if not ownerType then return nil, err end
|
||
|
||
local domain, nameErr = D.CheckName(rawDomain)
|
||
if not domain then return nil, nameErr end
|
||
|
||
if DB.GetSiteByDomain(domain) then
|
||
return nil, 'diese Domaene ist bereits vergeben'
|
||
end
|
||
|
||
local realmOk, realmErr = D.EnsureRealm(domain, title, ownerId)
|
||
if not realmOk then return nil, realmErr end
|
||
|
||
local site = D.CreateSiteWithHome(domain, title, ownerType, ownerId, FW.GetIdentifier(src))
|
||
if not site then return nil, 'Speichern fehlgeschlagen' end
|
||
|
||
-- Bei der Selbstanmeldung gibt es niemanden, der einen Zugang einrichten
|
||
-- koennte. Also wird er hier erzeugt und einmalig angezeigt.
|
||
local username = 'admin@' .. domain
|
||
local password = D.RandomPassword()
|
||
local accountId = ICWeb.Auth.CreateAccount({
|
||
username = username,
|
||
domain = domain,
|
||
role = 'admin',
|
||
display_name = title or domain,
|
||
password = password,
|
||
must_change = true,
|
||
created_by = 'selfservice',
|
||
})
|
||
|
||
ICWeb.Log.Write(src, 'domain.registered', { domain = domain, owner = ownerId })
|
||
|
||
return site, nil, accountId and { username = username, password = password } or nil
|
||
end
|
||
|
||
-- Domaene, Startseite und Rueckgabe in einem. Beide Anlagewege gehen hier durch,
|
||
-- damit eine neue Domaene nie ohne Startseite dasteht.
|
||
function D.CreateSiteWithHome(domain, title, ownerType, ownerId, createdBy)
|
||
local siteId = DB.CreateSite({
|
||
domain = domain,
|
||
title = tostring(title or domain):sub(1, 120),
|
||
theme = Config.DefaultTheme,
|
||
owner_type = ownerType or 'job',
|
||
owner_id = ownerId,
|
||
published = false,
|
||
created_by = createdBy,
|
||
})
|
||
if not siteId then return nil end
|
||
|
||
DB.SavePage(siteId, 'home', title or domain, json.encode({
|
||
{ type = 'heading', text = title or domain, level = 1 },
|
||
{ type = 'text', text = 'Diese Seite wurde noch nicht bearbeitet.' },
|
||
}), 0)
|
||
|
||
return DB.GetSiteById(siteId)
|
||
end
|
||
|
||
-- Aussprechbares Startpasswort. Es muss beim ersten Anmelden geaendert werden,
|
||
-- deshalb zaehlt Merkbarkeit hier mehr als Laenge.
|
||
function D.RandomPassword()
|
||
local words = { 'anker', 'birke', 'delta', 'falke', 'gipfel', 'hafen',
|
||
'insel', 'kompass', 'lotse', 'nordwind', 'orkan', 'rebe' }
|
||
return words[math.random(#words)] .. tostring(math.random(100, 999))
|
||
end
|