Skip to main content

AccessKickPolicy

A policy whose consequence is kicking. What triggers it is the binding.

Behaviour and application are separated on purpose: kicking is one thing, and "when someone is not staff" is another. Bind them at the registration site, where both halves read in a line:

maid:GiveTask(accessPolicyService:RegisterPolicy(
	maid:Add(AccessKickPolicy.whenFactIs(serviceBag, "kick-on-non-admin", AccessFactNames.PLAYER_IS_ADMIN, false, {
		message = "This place is currently limited to the development team.",
	}))
))

maid:GiveTask(accessPolicyService:RegisterPolicy(
	maid:Add(AccessKickPolicy.whenFeatureDisallowed(serviceBag, "kick-without-chapter-access", MyFeatures.Chapters))
))

GOTCHA, and the reason this is a class rather than a pattern to copy: a kick never fires on an unanswered input. "We could not find out whether you are staff" is not "you are not staff", and a policy that kicked on the difference would empty the server every time a lookup hiccupped. Both bindings below encode that once -- whenFactIs matches the value exactly, so unresolved never equals false; whenFeatureDisallowed skips AccessStateUtils.isUnresolved, which an author writing not isAllowed(state) by hand would silently get wrong.

Types

AccessKickTrigger

type AccessKickTrigger = (AccessPolicyContext) → Observable<boolean>

Emits true when this player should be kicked. Anything it never emits true for never kicks.

Functions

new

AccessKickPolicy.new(
serviceBagServiceBag,
optionsAccessKickPolicyOptions
) → AccessPolicy

The general form. Prefer AccessKickPolicy.whenFactIs or AccessKickPolicy.whenFeatureDisallowed -- they read better and they get the unresolved rule right without you having to remember it.

whenFactIs

AccessKickPolicy.whenFactIs(
serviceBagServiceBag,
policyNamestring,
factNamestring,
valueboolean,
options{
messagestring?,
isEnabledByDefaultboolean?
}?
) → AccessPolicy

Kicks while a fact reads exactly this value.

AccessKickPolicy.whenFactIs("kick-on-non-admin", "playerIsAdmin", false) reads as what it does: kick when playerIsAdmin is false. Unresolved is not false, so an unanswered lookup kicks nobody.

whenFeatureDisallowed

AccessKickPolicy.whenFeatureDisallowed(
serviceBagServiceBag,
policyNamestring,
featureAccessFeature,
options{
messagestring?,
isEnabledByDefaultboolean?
}?
) → AccessPolicy

Kicks while a feature refuses this player for a real reason.

Skips unresolved, which is the whole trap: an unresolved state is a disallowed state, so the obvious not isAllowed(state) would kick everyone whose lookup had not landed yet.

kick

AccessKickPolicy.kick(
playerPlayer,
messagestring
) → ()

Kicks the player, mock or otherwise. A PlayerMock has no native Kick, and a policy that only works against real players cannot be tested without a real session.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "The general form. Prefer [AccessKickPolicy.whenFactIs] or\n[AccessKickPolicy.whenFeatureDisallowed] -- they read better and they get the unresolved rule right\nwithout you having to remember it.",
            "params": [
                {
                    "name": "serviceBag",
                    "desc": "",
                    "lua_type": "ServiceBag"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "AccessKickPolicyOptions"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 70,
                "path": "src/access/src/Shared/Implementations/Policy/AccessKickPolicy.lua"
            }
        },
        {
            "name": "whenFactIs",
            "desc": "Kicks while a fact reads exactly this value.\n\n`AccessKickPolicy.whenFactIs(\"kick-on-non-admin\", \"playerIsAdmin\", false)` reads as what it does:\nkick when playerIsAdmin is false. Unresolved is not false, so an unanswered lookup kicks nobody.",
            "params": [
                {
                    "name": "serviceBag",
                    "desc": "",
                    "lua_type": "ServiceBag"
                },
                {
                    "name": "policyName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "factName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "boolean"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "{ message: string?, isEnabledByDefault: boolean? }?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 108,
                "path": "src/access/src/Shared/Implementations/Policy/AccessKickPolicy.lua"
            }
        },
        {
            "name": "whenFeatureDisallowed",
            "desc": "Kicks while a feature refuses this player for a real reason.\n\nSkips unresolved, which is the whole trap: an unresolved state *is* a disallowed state, so the obvious\n`not isAllowed(state)` would kick everyone whose lookup had not landed yet.",
            "params": [
                {
                    "name": "serviceBag",
                    "desc": "",
                    "lua_type": "ServiceBag"
                },
                {
                    "name": "policyName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "feature",
                    "desc": "",
                    "lua_type": "AccessFeature"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "{ message: string?, isEnabledByDefault: boolean? }?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 145,
                "path": "src/access/src/Shared/Implementations/Policy/AccessKickPolicy.lua"
            }
        },
        {
            "name": "kick",
            "desc": "Kicks the player, mock or otherwise. A [PlayerMock] has no native `Kick`, and a policy that only works\nagainst real players cannot be tested without a real session.",
            "params": [
                {
                    "name": "player",
                    "desc": "",
                    "lua_type": "Player"
                },
                {
                    "name": "message",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 175,
                "path": "src/access/src/Shared/Implementations/Policy/AccessKickPolicy.lua"
            }
        }
    ],
    "properties": [],
    "types": [
        {
            "name": "AccessKickTrigger",
            "desc": "Emits true when this player should be kicked. Anything it never emits true for never kicks.",
            "lua_type": "(AccessPolicyContext) -> Observable<boolean>",
            "source": {
                "line": 50,
                "path": "src/access/src/Shared/Implementations/Policy/AccessKickPolicy.lua"
            }
        }
    ],
    "name": "AccessKickPolicy",
    "desc": "A policy whose consequence is kicking. What *triggers* it is the binding.\n\nBehaviour and application are separated on purpose: kicking is one thing, and \"when someone is not\nstaff\" is another. Bind them at the registration site, where both halves read in a line:\n\n```lua\nmaid:GiveTask(accessPolicyService:RegisterPolicy(\n\tmaid:Add(AccessKickPolicy.whenFactIs(serviceBag, \"kick-on-non-admin\", AccessFactNames.PLAYER_IS_ADMIN, false, {\n\t\tmessage = \"This place is currently limited to the development team.\",\n\t}))\n))\n\nmaid:GiveTask(accessPolicyService:RegisterPolicy(\n\tmaid:Add(AccessKickPolicy.whenFeatureDisallowed(serviceBag, \"kick-without-chapter-access\", MyFeatures.Chapters))\n))\n```\n\nGOTCHA, and the reason this is a class rather than a pattern to copy: **a kick never fires on an\nunanswered input.** \"We could not find out whether you are staff\" is not \"you are not staff\", and a\npolicy that kicked on the difference would empty the server every time a lookup hiccupped. Both\nbindings below encode that once -- `whenFactIs` matches the value exactly, so unresolved never equals\n`false`; `whenFeatureDisallowed` skips [AccessStateUtils.isUnresolved], which an author writing\n`not isAllowed(state)` by hand would silently get wrong.",
    "source": {
        "line": 29,
        "path": "src/access/src/Shared/Implementations/Policy/AccessKickPolicy.lua"
    }
}