> For the complete documentation index, see [llms.txt](https://atiysus-organization.gitbook.io/aty-scripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://atiysus-organization.gitbook.io/aty-scripts/weapon-editor/getting-started/permissions.md).

# Permissions

## Permissions

There are three admin scopes and two player-facing gates.

### The three admin scopes

| Scope   | Opens                                       | Default groups        | Default ACE        |
| ------- | ------------------------------------------- | --------------------- | ------------------ |
| `lab`   | Balance Lab, workbench management, showcase | `god`, `admin`        | `gunsmith.balance` |
| `mod`   | The skin moderation queue                   | `god`, `admin`, `mod` | `gunsmith.mod`     |
| `range` | The Shooting Range                          | `god`, `admin`, `mod` | `gunsmith.range`   |

```lua
Config.Access = {
    lab   = { groups = { 'god', 'admin' },        ace = 'gunsmith.balance' },
    mod   = { groups = { 'god', 'admin', 'mod' }, ace = 'gunsmith.mod' },
    range = { groups = { 'god', 'admin', 'mod' }, ace = 'gunsmith.range',
              zones = { 'shooting_range' } },
}
```

### Two layers, either one grants

Each scope is checked twice, and **either check passing is enough**:

1. **`groups`** — your framework's own permission system.
   * QBCore / Qbox: `QBCore.Functions.HasPermission(src, groups)`, granted with `/setperm`
   * ESX: `xPlayer.getGroup()` must be in the list, granted through the `users.group` column
2. **`ace`** — the standalone path and the fallback.

   ```cfg
   add_ace group.admin gunsmith.balance allow
   ```

When a framework is present, `groups` is asked first. If it says no, `ace` is still tried. So a server can grant access either way, and mixing the two is fine.

{% hint style="info" %}
Commands are registered **through your framework's own command API** where one exists — `QBCore.Commands.Add` on QBCore, `ESX.RegisterCommand` on ESX. That means an unauthorised player does not merely get refused: the command does not appear in their chat suggestions at all. The scope is then checked a second time inside the handler, so granting the generated ACE by hand does not bypass the rule.
{% endhint %}

### When a grant does not work

```
/gunsmith:whoami
```

This is deliberately **not** permission-gated — the whole point is to answer "why don't I have permission". It prints, for every scope, which groups were tested, which ACE object was tested, the result of each, and which path granted access if any did.

The two usual answers:

* **The grant went to the framework, and the framework's own check is failing.** Add the ACE as well — either path is enough.
* **The ACE was added to a principal the player is not in.** `add_ace group.admin` only helps a player who is actually in `group.admin`. Grant the licence directly to test:

  ```cfg
  add_ace identifier.license:YOUR_LICENSE_HERE gunsmith.balance allow
  ```

### Player-facing gates

#### Reaching the workshop

```lua
Config.Access.workshop = {
    anywhere    = false,
    zones       = {},
    target      = true,
    command     = false,
    commandName = 'gunsmith',
}
```

| Field      | Meaning                                                                       |
| ---------- | ----------------------------------------------------------------------------- |
| `anywhere` | Skip all location checks. Off by default                                      |
| `zones`    | Fixed workshop points, naming entries in `Config.Zones`. **Empty by default** |
| `target`   | Use `ox_target` / `qb-target` for the interaction; falls back to a marker     |
| `command`  | Enable `/gunsmith` from anywhere. Off by default                              |

{% hint style="warning" %}
`zones` is empty and `command` is off on purpose. A fixed workshop point is open to **everyone** and obeys no job rule, and a global command makes the workbench system decorative — everyone opens the editor anywhere and the job restrictions stop meaning anything.

Turn them on only if that is the server you want. `command = true` is still subject to the rest of `workshop`; "on" does not mean "open to everyone".
{% endhint %}

#### Reaching the Skin Studio

```lua
Config.Access.studio = { requireItem = nil, jobs = nil }
```

| Field         | Meaning                                                                      |
| ------------- | ---------------------------------------------------------------------------- |
| `requireItem` | An inventory item the player must hold, e.g. `'design_kit'`. `nil` = no item |
| `jobs`        | Restrict the studio to certain jobs. `nil` = everyone                        |

#### Per-workbench access

Beyond all of the above, every individual workbench carries its own rule — public, a specific citizen, a job, or a job plus a minimum grade. That rule is always enforced on the server. See Workbenches.

### Hooks

Server-side, in `server/config.lua`, never sent to clients:

| Hook            | Signature                 | Use                                               |
| --------------- | ------------------------- | ------------------------------------------------- |
| `canCustomize`  | `(src, serial, category)` | Veto an individual edit                           |
| `priceFor`      | `(src, serial, changes)`  | Return the price of an edit                       |
| `onPayment`     | `(src, amount)`           | Take the money; return `false` to refuse the edit |
| `canModerate`   | `(src)`                   | Who may review skins                              |
| `skinValidator` | `(def)`                   | Reject a design — banned imagery, for example     |

These are the cut points for attaching your own economy and rules without touching the script.
