ic-web/server/domains.lua

221 lines
9.3 KiB
Lua
Raw Permalink Normal View History

-- ─────────────────────────────────────────────────────────────────────────────
-- 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