Команды администратора: как раздать права и не отдать игру
Как сделать админ-команды, которые нельзя вызвать чужим клиентом: где хранить список прав, почему проверка при входе не работает и что писать в журнал.
Сложно 20 минут проверен 34 дня назад
Что понадобится
Опубликованная игра и основы Luau. Понадобится понимание клиента и сервера — оно здесь главное.
Текст гайда «Команды администратора: как раздать права и не отдать игру»
Содержание
Админ-команды появляются в игре в тот момент, когда автор перестаёт справляться один: нужно кого-то выгнать, что-то перезапустить, кому-то выдать пропущенную награду. Команды пишутся за вечер и почти всегда — с одной и той же дырой.
Дыра выглядит так: клиент проверяет, админ ли игрок, и, если да, показывает панель и отправляет команду на сервер. Сервер команду выполняет — он же «получил её от панели, а панель показывается только админам». Никакой панели у нападающего нет. Есть сообщение, отправленное напрямую.
Правило, которое заменяет весь остальной гайд
Права проверяются в момент выполнения команды и на сервере. Не при входе в игру, не при показе кнопки, не «один раз в начале». Каждый вызов — своя проверка, каждый раз заново.
Шаг 1. Список прав живёт на сервере#
Первое решение — где хранить, кто админ. Вариантов три, и они по-разному удобны.
| Способ | Когда подходит | Что учесть |
|---|---|---|
Модуль в ServerStorage со списком UserId | Игра одного автора и пары друзей | Изменения только через обновление игры |
| Ранг в группе Roblox | Команда, которая меняется (как её собрать — в гайде про Team Create и группу) | Проверка ходит в сеть — нужен pcall и кэш |
Отдельное хранилище DataStore | Права выдаются прямо в игре | Выдавать права умеет только тот, у кого они уже есть |
Начинать стоит с первого: он самый простой и самый надёжный. Главное — UserId, а не имя. Имя игрок меняет, и в этот день админом становится тот, кто успел занять освободившийся ник.
-- Числовые идентификаторы, а не имена: имя можно сменить,
-- UserId — нет.
local LEVELS = {
[1234567] = "owner",
[7654321] = "moderator",
}
local RIGHTS = {
owner = { kick = true, respawn = true, announce = true, give = true },
moderator = { kick = true, respawn = true, announce = true, give = false },
}
local Admins = {}
function Admins.levelOf(player)
return LEVELS[player.UserId]
end
function Admins.can(player, action)
local level = LEVELS[player.UserId]
if level == nil then
return false
end
return RIGHTS[level][action] == true
end
return AdminsПрава по действию, а не по человеку
Проверка вида «этот игрок админ» рано или поздно упирается в вопрос «а вот этому можно выдавать предметы?». Проверка вида «этому игроку можно вот это действие» отвечает на него сразу и без переписывания.
Шаг 2. Один канал и одна точка проверки#
Заводим один RemoteEvent в ReplicatedStorage — например, AdminCommand. Не по событию на команду: чем больше входов, тем больше шансов, что в одном из них забудут проверку. Один вход — одно место, где проверка обязана быть.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local Admins = require(ServerStorage.Admins)
local channel = ReplicatedStorage:WaitForChild("AdminCommand")
-- Команда описывается правом, которое она требует,
-- и функцией. Забыть проверку тут негде: право
-- лежит рядом с самой командой.
local COMMANDS = {
kick = {
right = "kick",
run = function(_, targetName, reason)
local target = Players:FindFirstChild(targetName)
if target == nil then
return false, "Игрок не найден"
end
if Admins.levelOf(target) ~= nil then
-- Админы не выгоняют друг друга: иначе один
-- обиженный модератор чистит сервер от своих.
return false, "Нельзя выгнать администратора"
end
target:Kick(reason or "Нарушение правил")
return true
end,
},
respawn = {
right = "respawn",
run = function(_, targetName)
local target = Players:FindFirstChild(targetName)
if target == nil then
return false, "Игрок не найден"
end
target:LoadCharacter()
return true
end,
},
}
-- Защита от лавины вызовов: даже у настоящего админа
-- команда не должна уходить сто раз в секунду.
local lastCall = {}
channel.OnServerEvent:Connect(function(player, name, ...)
if type(name) ~= "string" then
return
end
local command = COMMANDS[name]
if command == nil then
return
end
-- Вот она, единственная и обязательная проверка.
if not Admins.can(player, command.right) then
warn(("%s (%d) попробовал команду %s без прав")
:format(player.Name, player.UserId, name))
return
end
local now = os.clock()
if lastCall[player] and now - lastCall[player] < 0.5 then
return
end
lastCall[player] = now
local ok, err = pcall(command.run, player, ...)
if not ok then
warn("Команда " .. name .. " упала: " .. tostring(err))
end
end)
Players.PlayerRemoving:Connect(function(player)
lastCall[player] = nil
end)Обрати внимание на то, чего в обработчике нет. Он не спрашивает клиента, админ ли тот. Он не принимает уровень прав в сообщении. Он не доверяет имени команды дальше, чем «есть такая в списке». Всё, что приходит снаружи, — данные, а не решения.
Три способа сломать собственную админку
Первый: проверить права один раз при входе и запомнить в переменной на клиенте. Второй: передавать в сообщении уровень доступа — его подставят. Третий: сделать «отладочную» команду без проверки и забыть её убрать перед публикацией. Третий встречается чаще первых двух вместе.
Шаг 3. Панель — это удобство, а не защита#
Кнопки админ-панели можно смело показывать только тем, у кого есть права: так удобнее. Но делать это нужно с ясной головой — панель прячется ради порядка на экране, а не ради безопасности. Сервер не должен становиться слабее оттого, что панель у кого-то всё-таки открылась.
-- Сервер, при входе игрока:
local rights = ReplicatedStorage:WaitForChild("AdminRights")
Players.PlayerAdded:Connect(function(player)
local level = Admins.levelOf(player)
if level ~= nil then
-- Клиенту отправляется только то, что ему рисовать.
-- Это подсказка интерфейсу, а не пропуск.
rights:FireClient(player, level)
end
end)Клиент получил уровень — нарисовал кнопки. Не получил — не нарисовал. Игрок, который нарисует их себе сам, ничего этим не добьётся: команды всё равно уйдут в тот же канал и упрутся в ту же проверку.
Шаг 4. Журнал#
Команды администратора — это единственное место в игре, где один человек может испортить вечер многим. Значит, каждое применение должно оставлять след: кто, что, кому и когда.
UserIdтого, кто выполнил, — не имя- Название команды и её аргументы
- Кого команда затронула
- Время и идентификатор сервера — иначе не понять, о каком раунде речь
- Отдельно — отказы: попытка без прав важнее удачного вызова
Простейший журнал — warn() в вывод сервера: он виден в Creator Hub. Серьёзнее — отдельный DataStore или запись во внешний сервис. Начинать можно с первого, но начинать нужно сразу: журнал, добавленный после первого инцидента, ничего про этот инцидент не расскажет.
Чужие админ-панели#
Готовых наборов админ-команд в свободном доступе много, и часть из них написана хорошо. Проблема не в качестве, а в том, что автор игры ставит к себе код, которого не читал, и раздаёт ему полные права над своей игрой.
Что посмотреть до того, как ставить чужую панель
Открой список админов внутри неё: не вписан ли туда кто-то, кроме тебя. Найди все RemoteEvent и проверь, что каждый обработчик начинается с проверки прав. Поищи обращения к сети — панели незачем ходить на сторонние адреса. Если читать чужой код тяжело, это само по себе ответ: свои двадцать строк надёжнее чужих двух тысяч.
Про что этот гайд — и про что он не про то#
Здесь речь о правах в своей игре: ты автор, ты решаешь, кто и что может. Это обычная работа разработчика.
Получение прав в чужой игре — не работа разработчика, а взлом, и заканчивается он баном аккаунта. Портал такие способы не публикует и не подсказывает — ни здесь, ни где-либо ещё. Как выглядят предложения «скрипта на админку» со стороны и почему за ними всегда стоит кража аккаунта, разобрано в разделе безопасности.
Проверь себя#
- Каждый обработчик команды начинается с проверки прав — не «панель», а именно обработчик
- В списке админов только идентификаторы, и в нём нет никого лишнего
- Ни одна команда не принимает от клиента уровень доступа
- Отладочные команды без проверки удалены до публикации
- Каждое применение и каждый отказ попадают в журнал
- Зайди в свою игру с обычного второго аккаунта и убедись, что панели нет, а прямой вызов канала ничего не даёт
Тот же принцип — «сервер решает, клиент просит» — лежит в основе магазина внутри игры и любой выдачи наград. Если он усвоен на админ-командах, остальное собирается по образцу.

Обсуждение
Пиши по делу и без личных данных. Ссылки на сторонние сайты, обмен и «бесплатные робуксы» мы скрываем — почему.
Комментарии пишут те, кто вошёл — так спокойнее всем.
ВойтиПока никто ничего не написал. Будь первым.