446 lines
18 KiB
Lua
446 lines
18 KiB
Lua
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
-- ic-web – Zugaenge und Anmeldung
|
|||
|
|
--
|
|||
|
|
-- Die gesamte Verwaltung laeuft ueber Anmeldungen, nicht ueber Job oder
|
|||
|
|
-- Serverrechte. Ein Zugang ist eine Mailadresse mit Passwort:
|
|||
|
|
--
|
|||
|
|
-- superadmin admin@liveinvader.ls legt Domaenen an und setzt je Domaene
|
|||
|
|
-- einen Admin ein
|
|||
|
|
-- admin <name>@<domaene> pflegt die Seiten seiner Domaene und
|
|||
|
|
-- legt dort weitere Zugaenge an
|
|||
|
|
-- editor <name>@<domaene> pflegt nur die Seiten
|
|||
|
|
--
|
|||
|
|
-- Wer angemeldet ist, bekommt zugleich das Postfach dieser Adresse. Mehrere
|
|||
|
|
-- Personen koennen sich mit demselben Zugang anmelden und dasselbe Postfach
|
|||
|
|
-- offen haben – das ist beabsichtigt, ein Firmenpostfach gehoert der Firma
|
|||
|
|
-- und nicht einer Person.
|
|||
|
|
--
|
|||
|
|
-- Das Passwort ist ein IC-Passwort. Es liegt gesalzen und gehasht in der
|
|||
|
|
-- Datenbank und wird nie an den Client geschickt, aber es ist keine
|
|||
|
|
-- Sicherheitsgrenze gegen jemanden mit Datenbankzugriff.
|
|||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
ICWeb = ICWeb or {}
|
|||
|
|
ICWeb.Auth = {}
|
|||
|
|
|
|||
|
|
local A = ICWeb.Auth
|
|||
|
|
local DB = ICWeb.DB
|
|||
|
|
local FW = ICWeb.FW
|
|||
|
|
|
|||
|
|
-- source → { username, role, domain, since, lastSeen }
|
|||
|
|
local sessions = {}
|
|||
|
|
|
|||
|
|
-- source → { [username] = true }: zusaetzlich per Passwort geoeffnete
|
|||
|
|
-- Postfaecher. Siehe A.OpenMailbox weiter unten.
|
|||
|
|
local openMailboxes = {}
|
|||
|
|
|
|||
|
|
-- ── Hilfen ──────────────────────────────────────────────────────────────────
|
|||
|
|
math.randomseed(os.time())
|
|||
|
|
|
|||
|
|
local function randomSalt()
|
|||
|
|
local out = {}
|
|||
|
|
for i = 1, 32 do
|
|||
|
|
out[i] = ('%x'):format(math.random(0, 15))
|
|||
|
|
end
|
|||
|
|
return table.concat(out)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Benutzername ist immer eine Mailadresse. Gibt (user, domain) oder nil.
|
|||
|
|
function A.SplitUsername(raw)
|
|||
|
|
local name = tostring(raw or ''):lower():gsub('%s', '')
|
|||
|
|
local user, domain = name:match('^([a-z0-9%.%-_]+)@([a-z0-9%-]+%.[a-z]+)$')
|
|||
|
|
if not user then return nil end
|
|||
|
|
return user, domain, name
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Anmelden ────────────────────────────────────────────────────────────────
|
|||
|
|
-- Gibt (session, nil) oder (nil, fehlertext) zurueck.
|
|||
|
|
function A.Login(src, rawUsername, password)
|
|||
|
|
local user, domain, username = A.SplitUsername(rawUsername)
|
|||
|
|
if not user then return nil, 'Benutzername muss eine Adresse sein, z. B. admin@liveinvader.ls' end
|
|||
|
|
|
|||
|
|
if type(password) ~= 'string' or password == '' then
|
|||
|
|
return nil, 'Kein Passwort angegeben.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Erst die Bremse, dann die Pruefung: sonst laesst sich der Zaehler
|
|||
|
|
-- umgehen, indem man einfach weiter probiert.
|
|||
|
|
local fails = DB.CountLoginFails(username, Config.Login.windowSeconds)
|
|||
|
|
if fails >= Config.Login.maxAttempts then
|
|||
|
|
return nil, ('Zu viele Fehlversuche. Bitte %d Minuten warten.')
|
|||
|
|
:format(math.ceil(Config.Login.windowSeconds / 60))
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local account = DB.CheckPassword(username, password)
|
|||
|
|
if not account then
|
|||
|
|
DB.AddLoginFail(username, FW.GetIdentifier(src))
|
|||
|
|
ICWeb.Log.Write(src, 'login.failed', { username = username }, 'warn')
|
|||
|
|
return nil, 'Benutzername oder Passwort falsch.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
if not ICWeb.Bool(account.active) then return nil, 'Dieser Zugang ist gesperrt.' end
|
|||
|
|
|
|||
|
|
DB.ClearLoginFails(username)
|
|||
|
|
DB.TouchLogin(account.id)
|
|||
|
|
|
|||
|
|
-- Alte Anmeldung dieses Spielers sauber beenden, damit das vorherige
|
|||
|
|
-- Postfach nicht offen bleibt.
|
|||
|
|
A.Logout(src)
|
|||
|
|
|
|||
|
|
sessions[src] = {
|
|||
|
|
id = account.id,
|
|||
|
|
username = account.username,
|
|||
|
|
role = account.role,
|
|||
|
|
domain = account.domain,
|
|||
|
|
display = account.display_name,
|
|||
|
|
mustChange = ICWeb.Bool(account.must_change),
|
|||
|
|
since = os.time(),
|
|||
|
|
lastSeen = os.time(),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
A.AttachMailbox(src, account.username, account.display_name)
|
|||
|
|
ICWeb.Log.Write(src, 'login.ok', { username = username, role = account.role })
|
|||
|
|
|
|||
|
|
return A.Public(src), nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function A.Logout(src)
|
|||
|
|
local session = sessions[src]
|
|||
|
|
if not session then return end
|
|||
|
|
|
|||
|
|
-- Das Postfach der Anmeldung nur schliessen, wenn es nicht zusaetzlich
|
|||
|
|
-- ueber "Postfach hinzufuegen" offen ist. Sonst nimmt das Abmelden von der
|
|||
|
|
-- Verwaltung dem Mailclient ein Postfach weg, das dort eigenstaendig
|
|||
|
|
-- freigeschaltet wurde.
|
|||
|
|
if not (openMailboxes[src] and openMailboxes[src][session.username]) then
|
|||
|
|
A.DetachMailbox(src, session.username)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
sessions[src] = nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Postfach an die Anmeldung koppeln ───────────────────────────────────────
|
|||
|
|
-- Der Zugriff wird beim Anmelden erteilt und beim Abmelden wieder entzogen.
|
|||
|
|
-- Dadurch haengt das Postfach am Zugang und nicht am Charakter, und mehrere
|
|||
|
|
-- Angemeldete koennen es gleichzeitig offen haben.
|
|||
|
|
--
|
|||
|
|
-- Beide Richtungen laufen ueber pcall: das Postfach ist eine Dreingabe zur
|
|||
|
|
-- Anmeldung. Wenn ic-mail zickt, soll man sich trotzdem anmelden und abmelden
|
|||
|
|
-- koennen – nur eben ohne Postfach.
|
|||
|
|
function A.AttachMailbox(src, address, displayName)
|
|||
|
|
if GetResourceState('ic-mail') ~= 'started' then return end
|
|||
|
|
|
|||
|
|
local identifier = FW.GetIdentifier(src)
|
|||
|
|
if not identifier then return end
|
|||
|
|
|
|||
|
|
local ok, err = pcall(function()
|
|||
|
|
exports['ic-mail']:EnsureSharedAddress(address, displayName, 'ic-web')
|
|||
|
|
exports['ic-mail']:GrantAddressAccess(address, identifier, FW.GetName(src), 'send', 'ic-web')
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
if not ok then
|
|||
|
|
print(('[ic-web] ^3Postfach %s konnte nicht geöffnet werden: %s^7')
|
|||
|
|
:format(tostring(address), tostring(err)))
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function A.DetachMailbox(src, address)
|
|||
|
|
if GetResourceState('ic-mail') ~= 'started' then return end
|
|||
|
|
|
|||
|
|
local identifier = FW.GetIdentifier(src)
|
|||
|
|
if not identifier then return end
|
|||
|
|
|
|||
|
|
local ok, err = pcall(function()
|
|||
|
|
exports['ic-mail']:RevokeAddressAccess(address, identifier)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
if not ok then
|
|||
|
|
print(('[ic-web] ^3Postfach %s konnte nicht geschlossen werden: %s^7')
|
|||
|
|
:format(tostring(address), tostring(err)))
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Weitere Postfaecher oeffnen ─────────────────────────────────────────────
|
|||
|
|
--
|
|||
|
|
-- Im Mailclient soll man mehrere Postfaecher gleichzeitig haben: das eigene,
|
|||
|
|
-- das der Firma, das der Behoerde. Jedes davon ist ein Zugang mit Passwort,
|
|||
|
|
-- also wird es genauso freigeschaltet wie die Anmeldung selbst – nur ohne
|
|||
|
|
-- Verwaltungsrechte. Wer die Zugangsdaten kennt, sieht das Postfach.
|
|||
|
|
--
|
|||
|
|
-- Bewusst getrennt von der Anmeldung an der Verwaltung: man kann zehn
|
|||
|
|
-- Postfaecher offen haben und trotzdem nur fuer eines Seiten pflegen duerfen.
|
|||
|
|
--
|
|||
|
|
-- source → { [username] = true } (oben deklariert)
|
|||
|
|
|
|||
|
|
-- Gibt (true, nil) oder (false, fehlertext) zurueck.
|
|||
|
|
function A.OpenMailbox(src, rawUsername, password)
|
|||
|
|
local user, _, username = A.SplitUsername(rawUsername)
|
|||
|
|
if not user then return false, 'Der Postfachname ist eine Adresse, z. B. presse@weazel.ls' end
|
|||
|
|
|
|||
|
|
if type(password) ~= 'string' or password == '' then
|
|||
|
|
return false, 'Kein Passwort angegeben.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Dieselbe Bremse wie bei der Anmeldung: sonst waere das Freischalten
|
|||
|
|
-- eines Postfachs der bequemere Weg, Passwoerter durchzuprobieren.
|
|||
|
|
if DB.CountLoginFails(username, Config.Login.windowSeconds) >= Config.Login.maxAttempts then
|
|||
|
|
return false, ('Zu viele Fehlversuche. Bitte %d Minuten warten.')
|
|||
|
|
:format(math.ceil(Config.Login.windowSeconds / 60))
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local account = DB.CheckPassword(username, password)
|
|||
|
|
if not account then
|
|||
|
|
DB.AddLoginFail(username, FW.GetIdentifier(src))
|
|||
|
|
ICWeb.Log.Write(src, 'mailbox.failed', { username = username }, 'warn')
|
|||
|
|
return false, 'Adresse oder Passwort falsch.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
if not ICWeb.Bool(account.active) then return false, 'Dieser Zugang ist gesperrt.' end
|
|||
|
|
|
|||
|
|
local boxes = openMailboxes[src] or {}
|
|||
|
|
local count = 0
|
|||
|
|
for _ in pairs(boxes) do count = count + 1 end
|
|||
|
|
if not boxes[account.username] and count >= (Config.MaxOpenMailboxes or 10) then
|
|||
|
|
return false, ('Höchstens %d zusätzliche Postfächer gleichzeitig.')
|
|||
|
|
:format(Config.MaxOpenMailboxes or 10)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DB.ClearLoginFails(username)
|
|||
|
|
|
|||
|
|
boxes[account.username] = true
|
|||
|
|
openMailboxes[src] = boxes
|
|||
|
|
|
|||
|
|
A.AttachMailbox(src, account.username, account.display_name)
|
|||
|
|
ICWeb.Log.Write(src, 'mailbox.opened', { username = account.username })
|
|||
|
|
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function A.CloseMailbox(src, rawUsername)
|
|||
|
|
local _, _, username = A.SplitUsername(rawUsername)
|
|||
|
|
if not username then return false, 'Unbekanntes Postfach.' end
|
|||
|
|
|
|||
|
|
local boxes = openMailboxes[src]
|
|||
|
|
if not boxes or not boxes[username] then
|
|||
|
|
return false, 'Dieses Postfach ist hier nicht geöffnet.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
boxes[username] = nil
|
|||
|
|
A.DetachMailbox(src, username)
|
|||
|
|
ICWeb.Log.Write(src, 'mailbox.closed', { username = username })
|
|||
|
|
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Welche Postfaecher hat dieser Spieler ueber ein Passwort geoeffnet? Der
|
|||
|
|
-- Mailclient braucht das, um genau bei diesen ein "Entfernen" anzubieten –
|
|||
|
|
-- das eigene und dienstlich zugewiesene Postfaecher gehoeren ihm nicht.
|
|||
|
|
function A.ListOpenMailboxes(src)
|
|||
|
|
local out = {}
|
|||
|
|
for username in pairs(openMailboxes[src] or {}) do out[#out + 1] = username end
|
|||
|
|
table.sort(out)
|
|||
|
|
return out
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Sitzung ─────────────────────────────────────────────────────────────────
|
|||
|
|
function A.Get(src)
|
|||
|
|
local session = sessions[src]
|
|||
|
|
if not session then return nil end
|
|||
|
|
|
|||
|
|
local idle = Config.Login.idleSeconds or 1800
|
|||
|
|
if idle > 0 and (os.time() - session.lastSeen) > idle then
|
|||
|
|
A.Logout(src)
|
|||
|
|
return nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
session.lastSeen = os.time()
|
|||
|
|
return session
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Was der Client ueber seine Anmeldung erfahren darf. Kein Passwort, kein Salt.
|
|||
|
|
function A.Public(src)
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
if not session then return nil end
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
id = session.id,
|
|||
|
|
username = session.username,
|
|||
|
|
role = session.role,
|
|||
|
|
domain = session.domain,
|
|||
|
|
display = session.display,
|
|||
|
|
mustChange = session.mustChange == true,
|
|||
|
|
}
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Rechte ──────────────────────────────────────────────────────────────────
|
|||
|
|
function A.IsSuperadmin(src)
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
return session ~= nil and session.role == 'superadmin'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Seiten dieser Domaene pflegen.
|
|||
|
|
function A.CanEdit(src, domain)
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
if not session then return false end
|
|||
|
|
if session.role == 'superadmin' then return true end
|
|||
|
|
return session.domain == tostring(domain or ''):lower()
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Zugaenge dieser Domaene verwalten. Editoren duerfen das nicht – sonst koennte
|
|||
|
|
-- sich jeder Editor selbst zum Admin machen.
|
|||
|
|
function A.CanManageAccounts(src, domain)
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
if not session then return false end
|
|||
|
|
if session.role == 'superadmin' then return true end
|
|||
|
|
return session.role == 'admin' and session.domain == tostring(domain or ''):lower()
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Zugaenge anlegen und aendern ────────────────────────────────────────────
|
|||
|
|
-- Gibt (accountId, nil) oder (nil, fehlertext) zurueck.
|
|||
|
|
function A.CreateAccount(data)
|
|||
|
|
local user, domain, username = A.SplitUsername(data.username)
|
|||
|
|
if not user then return nil, 'Benutzername muss eine Adresse sein, z. B. presse@weazel.ls' end
|
|||
|
|
|
|||
|
|
if data.role ~= 'admin' and data.role ~= 'editor' and data.role ~= 'superadmin' then
|
|||
|
|
return nil, 'Unbekannte Rolle.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Der superadmin haengt an keiner Domaene, alle anderen an genau ihrer.
|
|||
|
|
if data.role ~= 'superadmin' and domain ~= data.domain then
|
|||
|
|
return nil, 'Der Benutzername muss auf die eigene Domäne enden.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local pwErr = A.CheckPassword(data.password)
|
|||
|
|
if pwErr then return nil, pwErr end
|
|||
|
|
|
|||
|
|
if DB.GetAccountByUsername(username) then
|
|||
|
|
return nil, 'Diesen Benutzernamen gibt es bereits.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Nicht als `role == 'superadmin' and nil or domain` schreiben: in Lua
|
|||
|
|
-- faellt der nil-Zweig durch und man bekaeme doch die Domaene.
|
|||
|
|
local accountDomain = domain
|
|||
|
|
if data.role == 'superadmin' then accountDomain = nil end
|
|||
|
|
|
|||
|
|
local id = DB.CreateAccount({
|
|||
|
|
username = username,
|
|||
|
|
domain = accountDomain,
|
|||
|
|
role = data.role,
|
|||
|
|
display_name = tostring(data.display_name or user):sub(1, 120),
|
|||
|
|
salt = randomSalt(),
|
|||
|
|
password = data.password,
|
|||
|
|
must_change = data.must_change and true or false,
|
|||
|
|
created_by = data.created_by,
|
|||
|
|
})
|
|||
|
|
if not id then return nil, 'Speichern fehlgeschlagen.' end
|
|||
|
|
|
|||
|
|
-- Das Postfach gibt es ab sofort, auch wenn sich noch niemand angemeldet hat.
|
|||
|
|
if GetResourceState('ic-mail') == 'started' then
|
|||
|
|
exports['ic-mail']:EnsureSharedAddress(username, data.display_name or user, 'ic-web')
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return id, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Gibt nil zurueck, wenn das Passwort taugt, sonst den Grund.
|
|||
|
|
function A.CheckPassword(password)
|
|||
|
|
if type(password) ~= 'string' then return 'Kein Passwort angegeben.' end
|
|||
|
|
if #password < (Config.Login.minPasswordLength or 6) then
|
|||
|
|
return ('Das Passwort muss mindestens %d Zeichen haben.')
|
|||
|
|
:format(Config.Login.minPasswordLength or 6)
|
|||
|
|
end
|
|||
|
|
if #password > 100 then return 'Das Passwort ist zu lang.' end
|
|||
|
|
return nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Nach dem Wechsel des eigenen Passworts soll die Aufforderung verschwinden,
|
|||
|
|
-- ohne dass sich der Zugang neu anmelden muss.
|
|||
|
|
function A.ClearMustChange(src)
|
|||
|
|
local session = sessions[src]
|
|||
|
|
if session then session.mustChange = false end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function A.SetPassword(accountId, password)
|
|||
|
|
local err = A.CheckPassword(password)
|
|||
|
|
if err then return false, err end
|
|||
|
|
|
|||
|
|
DB.SetPassword(accountId, randomSalt(), password)
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Aufraeumen ──────────────────────────────────────────────────────────────
|
|||
|
|
AddEventHandler('playerDropped', function()
|
|||
|
|
local src = source
|
|||
|
|
for username in pairs(openMailboxes[src] or {}) do
|
|||
|
|
A.DetachMailbox(src, username)
|
|||
|
|
end
|
|||
|
|
openMailboxes[src] = nil
|
|||
|
|
A.Logout(src)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- Beim Herunterfahren wird NICHT mehr aufgeraeumt.
|
|||
|
|
--
|
|||
|
|
-- A.Logout ruft ic-mail auf, und ic-mail wartet dort auf eine Datenbankantwort.
|
|||
|
|
-- Waehrend die Resource stoppt, laesst sich die wartende Koroutine nicht mehr
|
|||
|
|
-- fortsetzen: die Abfrage laeuft durch, das Ergebnis kommt aber ins Leere und
|
|||
|
|
-- der Server meldet "Execution of function reference in script host failed"
|
|||
|
|
-- – zugeordnet zu ic-mail, obwohl der Ausloeser hier liegt.
|
|||
|
|
--
|
|||
|
|
-- Aufgeraeumt wird stattdessen beim Start (A.CleanupStaleMailAccess). Das
|
|||
|
|
-- deckt auch den Fall ab, den ein Stop-Handler nie erwischt: den Absturz.
|
|||
|
|
AddEventHandler('onResourceStop', function(res)
|
|||
|
|
if res ~= GetCurrentResourceName() then return end
|
|||
|
|
sessions, openMailboxes = {}, {}
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- Zugriffsrechte, die frueher einmal ueber eine Anmeldung erteilt wurden.
|
|||
|
|
-- Beim Start gibt es keine Anmeldung, also darf auch kein Recht mehr offen sein.
|
|||
|
|
function A.CleanupStaleMailAccess()
|
|||
|
|
if GetResourceState('ic-mail') ~= 'started' then return end
|
|||
|
|
|
|||
|
|
local ok, removed = pcall(function()
|
|||
|
|
return exports['ic-mail']:RevokeAccessGrantedBy('ic-web')
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
if ok and tonumber(removed) and tonumber(removed) > 0 then
|
|||
|
|
print(('[ic-web] %d verwaiste Postfachzugriffe entfernt.'):format(tonumber(removed)))
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Ersteinrichtung ─────────────────────────────────────────────────────────
|
|||
|
|
-- Ohne einen ersten Zugang kaeme niemand je in die Verwaltung hinein.
|
|||
|
|
function A.EnsureMainAdmin()
|
|||
|
|
local cfg = Config.MainAdmin
|
|||
|
|
if DB.GetAccountByUsername(cfg.username) then return end
|
|||
|
|
|
|||
|
|
-- Die Domaene des Anbieters muss es geben, sonst haengt das Postfach ins Leere.
|
|||
|
|
if GetResourceState('ic-mail') == 'started' then
|
|||
|
|
local _, realmDomain = A.SplitUsername(cfg.username)
|
|||
|
|
if realmDomain and not exports['ic-mail']:GetRealmByName(realmDomain) then
|
|||
|
|
exports['ic-mail']:CreateRealm({
|
|||
|
|
realm = realmDomain,
|
|||
|
|
type = 'system',
|
|||
|
|
display_name = cfg.providerName or realmDomain,
|
|||
|
|
})
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local id, err = A.CreateAccount({
|
|||
|
|
username = cfg.username,
|
|||
|
|
role = 'superadmin',
|
|||
|
|
display_name = cfg.providerName or 'Administration',
|
|||
|
|
password = cfg.initialPassword,
|
|||
|
|
must_change = true,
|
|||
|
|
created_by = 'system',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if not id then
|
|||
|
|
print(('[ic-web] ^1Hauptzugang konnte nicht angelegt werden: %s^7'):format(tostring(err)))
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
print('[ic-web] ^3Hauptzugang angelegt:^7 ' .. cfg.username)
|
|||
|
|
print('[ic-web] ^3Startpasswort:^7 ' .. cfg.initialPassword
|
|||
|
|
.. ' – bitte nach der ersten Anmeldung ändern (/webpasswd).')
|
|||
|
|
end
|