Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1551,3 +1551,31 @@ Note: it will show or hide spells in vehicle spell bar.

- 0 (If target needs to be on land)

### Breadcrumb Quests

Breadcrumb quests are optional quests that lead players to a quest hub or NPC but can be skipped if the player goes directly to the destination. Unlike mutually exclusive quests, a breadcrumb should only become unavailable after the player picks up or completes the main quest - NOT vice versa.

**Do NOT use [ExclusiveGroup](quest_template_addon#exclusivegroup)** for breadcrumb quests. ExclusiveGroup makes quests mutually exclusive in both directions, which breaks quest progression if the player does the breadcrumb first.

**Correct approach:** Use CONDITION_SOURCE_TYPE_QUEST_AVAILABLE (19) with CONDITION_QUESTTAKEN (9) and CONDITION_QUESTREWARDED (8) to hide the breadcrumb when the main quest is taken or completed.

**Example:** Quest 11287 "Find Sage Mistwalker" is a breadcrumb leading to quest 11286 "The Artifacts of Steel Gate". Players can skip the breadcrumb and go directly to 11286.

```sql
-- Make breadcrumb unavailable if main quest is taken or rewarded
DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 19 AND `SourceEntry` = 11287;
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
(19, 0, 11287, 0, 0, 9, 0, 11286, 0, 0, 1, 0, 0, '', 'Find Sage Mistwalker - Not available if The Artifacts of Steel Gate is taken'),
(19, 0, 11287, 0, 0, 8, 0, 11286, 0, 0, 1, 0, 0, '', 'Find Sage Mistwalker - Not available if The Artifacts of Steel Gate is rewarded');
```

**Explanation:**
- `SourceTypeOrReferenceId = 19`: CONDITION_SOURCE_TYPE_QUEST_AVAILABLE - controls when quest appears
- `SourceEntry = 11287`: The breadcrumb quest ID
- `ConditionTypeOrReference = 9`: CONDITION_QUESTTAKEN - checks if quest is in player's log
- `ConditionTypeOrReference = 8`: CONDITION_QUESTREWARDED - checks if quest was completed
- `ConditionValue1 = 11286`: The main quest ID to check against
- `NegativeCondition = 1`: Inverts the condition (quest NOT available if condition is true)

Both conditions are in ElseGroup 0, so they act as a logical AND - the breadcrumb is only available if the player has NOT taken AND has NOT been rewarded the main quest.

5 changes: 5 additions & 0 deletions docs/database-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@

## M

- [module_rbac_permissions](module_rbac_permissions)
- [motd](motd)

## R
- [rbac_account_permissions](rbac_account_permissions)
- [rbac_default_permissions](rbac_default_permissions)
- [rbac_linked_permissions](rbac_linked_permissions)
- [rbac_permissions](rbac_permissions)
- [realmcharacters](realmcharacters)
- [realmlist](realmlist)

Expand Down
1 change: 1 addition & 0 deletions docs/documentation-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ redirect_from: /documentation_index
* [Monitoring AzerothCore with Grafana](monitoring-azerothcore-with-grafana)
* [MySQL types (C++)](mysqltypescpp)
* [Project Versioning](project-versioning)
* [RBAC](rbac)
* [Remote Access](remote-access)
* [Sniffing & Parsing](sniffing-and-parsing)
* [Spell Effects Reference](spell-effects-reference)
Expand Down
121 changes: 121 additions & 0 deletions docs/module_rbac_permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# module\_rbac\_permissions

[<-Back-to:Auth](database-auth)

**The \`module\_rbac\_permissions\` table**

This table allows modules to register their own RBAC permissions without risk of ID collisions with core permissions or other modules. Each module uses local IDs (1, 2, 3, ...) and the table auto-assigns global IDs starting at 100000.

For a system overview, see [RBAC](rbac).

**Table Structure**

| Field | Type | Attributes | Key | Null | Default | Extra | Comment |
| -------------- | ------------ | ---------- | ------ | ---- | ------- | -------------- | ------------------------------------ |
| [module](#module) | VARCHAR(255) | SIGNED | PRI | NO | | | Module dir name, e.g. mod-cfbg |
| [id](#id) | INT | UNSIGNED | PRI | NO | | | Module-local permission id |
| [global_id](#globalid) | INT | UNSIGNED | UNIQUE | NO | | AUTO_INCREMENT | Auto-assigned global permission ID |
| [name](#name) | VARCHAR(100) | SIGNED | | NO | | | Permission name |

The primary key is the composite `(module, id)`, which prevents cross-module ID collisions. The `global_id` column has a separate `UNIQUE` index and auto-increments starting at 100000.

**Description of the fields**

### module

The module directory name (e.g. `mod-cfbg`, `mod-eluna`). This must match the module's directory name under `modules/`.

### id

A module-local permission ID. Each module manages its own ID sequence starting at 1. The same local ID can be used by different modules without conflict because the primary key includes the module name.

### global\_id

An auto-assigned globally unique permission ID used by the core RBAC system. The `AUTO_INCREMENT` starts at 100000 to avoid collisions with core permission IDs (1–924). Modules should never set this value manually — it is assigned by the database on insert.

### name

A human-readable name for the permission. By convention, command permissions use the format `Command: .commandname subcommand`.

## Module Integration Guide

### Step 1: Register permissions in SQL

Create a SQL file in your module's `data/sql/db-auth/` directory:

```sql
INSERT IGNORE INTO `module_rbac_permissions` (`module`, `id`, `name`) VALUES
('mod-example', 1, 'Command: .example hello'),
('mod-example', 2, 'Command: .example info');
```

Use `INSERT IGNORE` so the SQL is safe to re-run.

### Step 2: Look up global IDs in C++

In your module's C++ code, use `AccountMgr` to translate local IDs to global IDs:

```cpp
#include "AccountMgr.h"

uint32 globalId = sAccountMgr->GetModulePermission("mod-example", 1);
```

Returns 0 if the permission is not found in the database.

### Step 3: Use in CommandScript

When defining commands, use the global ID as the permission for each command:

```cpp
static uint32 GetPermission(uint32 localId)
{
return sAccountMgr->GetModulePermission("mod-example", localId);
}

std::vector<ChatCommand> GetCommands() const override
{
static std::vector<ChatCommand> exampleCommandTable =
{
{ "hello", GetPermission(1), false, &HandleHelloCommand, "" },
{ "info", GetPermission(2), false, &HandleInfoCommand, "" },
};

static std::vector<ChatCommand> commandTable =
{
{ "example", SEC_PLAYER, false, nullptr, "", exampleCommandTable },
};

return commandTable;
}
```

### Step 4: Assign permissions

Module permissions need to be assigned to roles or accounts before anyone can use them.

**Assign to a role** — link the module permissions into a command role via [rbac_linked_permissions](rbac_linked_permissions). Everyone who inherits that role will gain access:

```sql
-- Grant all mod-example commands to the GM Commands role (197)
INSERT IGNORE INTO `rbac_linked_permissions` (`id`, `linkedId`)
SELECT 197, `global_id`
FROM `module_rbac_permissions`
WHERE `module` = 'mod-example';
```

**Assign to a specific account** — insert into [rbac_account_permissions](rbac_account_permissions) or use the `.rbac` command in-game:

```sql
-- Grant mod-example's first command to account 5 on all realms
INSERT INTO `rbac_account_permissions` (`accountId`, `permissionId`, `granted`, `realmId`)
SELECT 5, `global_id`, 1, -1
FROM `module_rbac_permissions`
WHERE `module` = 'mod-example' AND `id` = 1;
```

Or in-game, once you know the global ID (e.g. 100001):

```
.rbac account grant 5 100001
```
2 changes: 2 additions & 0 deletions docs/quest_template_addon.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ Allows to define a group of quests of which all must be completed and rewarded t

Note: All quests that use an ExclusiveGroup must also have entries in [pool_template](pool_template) and [pool_quest](quest_template#examples-dealing-with-quests) for examples.

**Important:** ExclusiveGroup should NOT be used for breadcrumb quests. Use [conditions](conditions#breadcrumb-quests) instead to properly handle optional lead-in quests.

### RewardMailTemplateID

If the quest gives as a reward an item from a possible list of items, the ID here corresponds to the proper loot template in [quest_mail_loot_template](loot_template). According to the rules in that loot template, items "looted" will be sent by mail at the completion of the quest.
Expand Down
109 changes: 109 additions & 0 deletions docs/rbac.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# RBAC

[<-Back-to:Documentation Index](documentation-index)

## Overview

Role-Based Access Control (RBAC) is AzerothCore's permission system. It provides fine-grained control over what each account can do — from individual commands to gameplay privileges like skipping the login queue or joining battlegrounds.

With RBAC you can:

- Control access to every command individually
- Create roles that bundle related permissions together
- Override specific permissions per account without changing their overall rank
- Let modules register their own permissions without conflicting with core IDs

## Architecture

The RBAC system is built on four concepts:

1. **Permissions** — Individual capabilities (e.g. "can use `.tele`", "skip queue", "join battlegrounds")
2. **Roles** — Permissions that link to other permissions, forming a group. Granting a role grants everything it contains
3. **Defaults** — Mappings from `account_access.gmlevel` to an initial RBAC role, so security levels map to permission sets automatically
4. **Account Overrides** — Per-account grants or denies that fine-tune access beyond the defaults

### Database Tables

| Table | Purpose |
| ----- | ------- |
| [rbac_permissions](rbac_permissions) | Defines all available permissions |
| [rbac_linked_permissions](rbac_linked_permissions) | Links roles to their child permissions |
| [rbac_default_permissions](rbac_default_permissions) | Maps security levels to default roles |
| [rbac_account_permissions](rbac_account_permissions) | Per-account overrides (grant/deny) |
| [module_rbac_permissions](module_rbac_permissions) | Module-registered permissions |

A convenience view `vw_rbac` joins the linked and default tables for easier querying.

## Permission ID Ranges

| Range | Purpose | Examples |
| ----- | ------- | ------- |
| 1–53 | Gameplay permissions | Instant logout, skip queue, join BG/arena/dungeon finder |
| 192–195 | Security-level roles | Administrator (192), Gamemaster (193), Moderator (194), Player (195) |
| 196–199 | Command roles | Admin Commands (196), GM Commands (197), Mod Commands (198), Player Commands (199) |
| 200–924 | Individual command permissions | One per `.command` |
| 100000+ | Module permissions | Auto-assigned via [module_rbac_permissions](module_rbac_permissions) |

## Role Hierarchy

Roles inherit from each other through linked permissions. Each higher role links to the one below it, so an Administrator automatically receives every permission a Player has.

```
Administrator (192)
├── Core admin perms (7, 21, 42, 43)
├── Admin Commands (196)
└── Gamemaster (193)
├── Core GM perms (45, 48, 52, 53)
├── GM Commands (197)
└── Moderator (194)
├── Core mod perms (1, 2, 9, 11, 13–47, 51, ...)
├── Mod Commands (198)
└── Player (195)
├── Core player perms (3, 4, 5, 6, 24, 49, 50)
└── Player Commands (199)
```

### Default Security Level Mapping

When a player logs in, their `account_access.gmlevel` determines which role they receive:

| gmlevel | Role | Permission ID |
| ------- | ---- | ------------- |
| 3 | Administrator | 192 |
| 2 | Gamemaster | 193 |
| 1 | Moderator | 194 |
| 0 | Player | 195 |

These defaults are stored in [rbac_default_permissions](rbac_default_permissions).

## Permission Resolution

When an account's permissions are calculated, the following steps occur:

1. **Collect grants** — Account-specific grants from [rbac_account_permissions](rbac_account_permissions) plus defaults from [rbac_default_permissions](rbac_default_permissions)
2. **Expand grants** — Each granted permission is expanded recursively through [rbac_linked_permissions](rbac_linked_permissions). If permission 192 links to 193, and 193 links to 194, all are included
3. **Collect denies** — Account-specific denies from [rbac_account_permissions](rbac_account_permissions)
4. **Expand denies** — Denied permissions are also expanded through linked permissions
5. **Subtract** — Final permissions = Expanded Grants − Expanded Denies

This means denying a role denies everything that role contains.

## Commands

The `.rbac` commands allow live management of account permissions without restarting the server.

| Command | Permission | Description |
| ------- | ---------- | ----------- |
| `.rbac account list <account>` | 202 | List granted, denied, and default permissions for an account |
| `.rbac account grant <account> <permId> [realmId]` | 203 | Grant a permission to an account |
| `.rbac account deny <account> <permId> [realmId]` | 204 | Deny a permission for an account |
| `.rbac account revoke <account> <permId> [realmId]` | 205 | Revoke a previously granted or denied permission |
| `.rbac list [permId]` | 206 | List all permissions, or show details for a specific permission |

Changes take effect immediately for online players.

## Module Integration

Modules can register their own RBAC permissions using the [module_rbac_permissions](module_rbac_permissions) table. Each module uses local IDs (1, 2, 3, ...) that are automatically mapped to global IDs starting at 100000, avoiding conflicts with core permission IDs and between modules.

See [module_rbac_permissions](module_rbac_permissions) for the full integration guide.
48 changes: 48 additions & 0 deletions docs/rbac_account_permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# rbac\_account\_permissions

[<-Back-to:Auth](database-auth)

**The \`rbac\_account\_permissions\` table**

This table stores per-account permission overrides. Use it to grant or deny specific permissions to individual accounts, beyond what their default security level provides.

For a system overview, see [RBAC](rbac).

**Table Structure**

| Field | Type | Attributes | Key | Null | Default | Extra | Comment |
| ----------------- | ---------- | ---------- | --- | ---- | ------- | ----- | ------------------------ |
| [accountId](#accountid) | INT | UNSIGNED | PRI | NO | | | Account id |
| [permissionId](#permissionid) | INT | UNSIGNED | PRI | NO | | | Permission id |
| [granted](#granted) | TINYINT(1) | SIGNED | | NO | 1 | | Granted = 1, Denied = 0 |
| [realmId](#realmid) | INT | SIGNED | PRI | NO | -1 | | Realm Id, -1 means all |

The `accountId` field has a foreign key to [account.id](account#id) with `ON DELETE CASCADE`.
The `permissionId` field has a foreign key to [rbac_permissions.id](rbac_permissions#id) with `ON DELETE CASCADE`.

**Description of the fields**

### accountId

The [account ID](account#id).

### permissionId

The permission ID from [rbac_permissions](rbac_permissions). This can be an individual permission or a role. Granting/denying a role affects all permissions linked to it.

### granted

Controls whether this entry is a grant or a deny:

| Value | Meaning |
| ----- | ------- |
| 1 | **Grant** — adds this permission to the account |
| 0 | **Deny** — removes this permission (and its linked permissions) from the account |

During [permission resolution](rbac#permission-resolution), denies are subtracted from grants after expansion. This means denying a role denies everything that role contains.

### realmId

The [realm ID](realmlist#id) this override applies to. Use `-1` to apply to all realms.

These overrides can be managed in-game with the [.rbac commands](rbac#commands).
42 changes: 42 additions & 0 deletions docs/rbac_default_permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# rbac\_default\_permissions

[<-Back-to:Auth](database-auth)

**The \`rbac\_default\_permissions\` table**

This table maps [account_access.gmlevel](account_access#gmlevel) security levels to default RBAC roles. When a player logs in, their security level determines which role they receive automatically.

For a system overview, see [RBAC](rbac).

**Table Structure**

| Field | Type | Attributes | Key | Null | Default | Extra | Comment |
| ----------------- | ------- | ---------- | --- | ---- | ------- | ----- | ------------------------ |
| [secId](#secid) | INT | UNSIGNED | PRI | NO | | | Security Level id |
| [permissionId](#permissionid) | INT | UNSIGNED | PRI | NO | | | Permission id |
| [realmId](#realmid) | INT | SIGNED | PRI | NO | -1 | | Realm Id, -1 means all |

The `permissionId` field has a foreign key to [rbac_permissions.id](rbac_permissions#id).

**Description of the fields**

### secId

The security level from [account_access.gmlevel](account_access#gmlevel).

### permissionId

The RBAC permission (role) to grant by default for this security level. The default assignments are:

| secId | permissionId | Role |
| ----- | ------------ | ---- |
| 0 | 195 | Player |
| 1 | 194 | Moderator |
| 2 | 193 | Gamemaster |
| 3 | 192 | Administrator |

Because roles chain through [rbac_linked_permissions](rbac_linked_permissions), granting Administrator (192) automatically includes Gamemaster, Moderator, and Player permissions.

### realmId

The [realm ID](realmlist#id) this default applies to. Use `-1` to apply to all realms.
Loading