@hexium-softworks/permissionsservice
v1.1.0
Published
A game-agnostic permissions package with composable boolean rules and modular checks for NevermoreEngine projects.
Downloads
267
Maintainers
Readme
PermissionsService
A game-agnostic Luau permissions package for Nevermore-style Roblox projects.
PermissionsService provides typed rule builders, boolean composition, a synchronous evaluator, and a registry for package-provided or game-provided checks. It does not depend on game-specific services or Nevermore's PermissionProvider. Client UI capability queries use Nevermore's game-agnostic @quenty/remoting and @quenty/promise packages.
Nevermore Usage
PermissionsService and PermissionsServiceClient are Nevermore services, so games should retrieve them through ServiceBag. Shared modules such as Permissions, PermissionsRegistry, and the built-in check modules can still be required directly when building rules or tests. This follows Nevermore's service lifecycle pattern: get services through ServiceBag, call Init(), configure services, then call Start().
-- ServerMain.server.lua
local require = require(loader).bootstrapGame(root)
local ServiceBag = require("ServiceBag")
local Permissions = require("Permissions")
local PermissionsService = require("PermissionsService")
local serviceBag = ServiceBag.new()
local permissionsService = serviceBag:GetService(PermissionsService)
serviceBag:Init()
-- Register game-specific checks and capabilities after Init.
-- Start can happen after this so remoting is exposed once configuration is ready.
permissionsService:RegisterCapability("CanSeeDeveloperTools", Permissions.Check("UserId", {
userIds = { 12345 },
}))
serviceBag:Start()-- ClientMain.client.lua
local require = require(loader).bootstrapGame(root)
local ServiceBag = require("ServiceBag")
local PermissionsServiceClient = require("PermissionsServiceClient")
local serviceBag = ServiceBag.new()
local permissionsServiceClient = serviceBag:GetService(PermissionsServiceClient)
serviceBag:Init()
serviceBag:Start()
local canSeeDeveloperTools = permissionsServiceClient:CanUseCapability("CanSeeDeveloperTools")Pure Rule Usage
local Permissions = require("Permissions")
local rule = Permissions.AnyOf(
Permissions.Check("UserId", {
userIds = { 12345 },
}),
Permissions.AllOf(
Permissions.Check("GroupRank", {
groupId = 123456,
minRank = 100,
}),
Permissions.NoneOf(
Permissions.Check("IsSuspended")
)
)
)
local allowed = Permissions.evaluate(rule, {
player = player,
}, registry)Empty composite rules fail closed. Permissions.AnyOf(), Permissions.AllOf(), and Permissions.NoneOf() all evaluate to false, so an empty config cannot accidentally grant access.
Registry
registry:RegisterCheck("HasRole", function(context, args)
return context.roles[args.role] == true
end)Use RegisterCheck() for unique names and SetCheck() when intentionally replacing a check.
Modular Game Checks
Games can keep gameplay-specific checks in their own ModuleScripts and register them during service startup. A useful convention is for each module to return a name and a check function.
-- Server/PermissionChecks/HasRole.lua
return {
Name = "HasRole",
Check = function(context, args)
local roles = context.roles
if type(roles) ~= "table" or type(args) ~= "table" then
return false
end
return roles[args.role] == true
end,
}-- Server/PermissionChecks/HasClearance.lua
return {
Name = "HasClearance",
Check = function(context, args)
local clearance = context.clearance
if type(clearance) ~= "number" or type(args) ~= "table" then
return false
end
return clearance >= args.minimum
end,
}-- Server/RegisterPermissionChecks.lua
local function registerPermissionChecks(registry, folder)
for _, moduleScript in folder:GetChildren() do
if moduleScript:IsA("ModuleScript") then
local definition = require(moduleScript)
registry:RegisterCheck(definition.Name, definition.Check)
end
end
end
return registerPermissionChecksThen the game service that owns player state can retrieve PermissionsService through its ServiceBag, build the permission context, and evaluate rules.
local Permissions = require("Permissions")
local registerPermissionChecks = require(script.Parent.RegisterPermissionChecks)
local permissionsService = serviceBag:GetService(require("PermissionsService"))
local registry = permissionsService:GetRegistry()
registerPermissionChecks(registry, script.Parent.PermissionChecks)
local openArmoryRule = Permissions.AnyOf(
Permissions.Check("HasRole", {
role = "Security",
}),
Permissions.Check("HasClearance", {
minimum = 3,
})
)
local context = {
player = player,
roles = RoleService:GetRoles(player),
clearance = ClearanceService:GetClearance(player),
}
local canOpenArmory = Permissions.evaluate(openArmoryRule, context, registry)For role inheritance, keep the inheritance logic inside the game check instead of adding game-specific concepts to this package.
-- Server/PermissionChecks/InheritsRole.lua
local ROLE_PARENTS = {
SeniorSecurity = "Security",
SecurityCommander = "SeniorSecurity",
}
local function inheritsRole(actualRole, requiredRole)
while actualRole ~= nil do
if actualRole == requiredRole then
return true
end
actualRole = ROLE_PARENTS[actualRole]
end
return false
end
return {
Name = "InheritsRole",
Check = function(context, args)
for role in context.roles do
if inheritsRole(role, args.role) then
return true
end
end
return false
end,
}UI Capabilities
Server code can expose named capabilities for client UI visibility without allowing clients to submit arbitrary permission rules. Client queries use Nevermore's remoting package behind the service API.
permissionsService:RegisterCapability("CanSeeDeveloperTools", Permissions.Check("UserId", {
userIds = { 12345 },
}))local canSeeDeveloperTools = permissionsServiceClient:CanUseCapability("CanSeeDeveloperTools")For UI flows that should not yield the current thread, use the promise API.
permissionsServiceClient:PromiseCanUseCapability("CanSeeDeveloperTools")
:Then(function(canSeeDeveloperTools)
print(canSeeDeveloperTools)
end)Capabilities are for presentation only. Server gameplay actions should still call the server service directly.
Built-In Checks
AlwaysNeverUserIdGroupRank
Gameplay concepts such as roles, department inheritance, teams, or clearance levels should be registered by the game.
