Skip to content

AdminRole

The AdminRole table implements role-based access control (RBAC) for the QS-Bridge platform. Each row maps a player to an administrative role level, defining what actions they are authorized to perform through the admin panel or direct reducer calls.


Scope

🌍 Global — Admin roles apply cluster-wide. An administrator's role level is the same regardless of which server they are managing. All game hooks and the admin panel subscribe to the full table.


Schema

Column Type Constraints Description
role_id u64 Primary Key, auto-increment Unique identifier for this role assignment
player_id String Unique Platform-wide player identifier
role AdminRoleLevel The assigned role level (enum)
granted_by String Identifier of the admin who granted this role
granted_at Timestamp When the role was granted

Rust Definition

#[spacetimedb::table(public, name = admin_role)]
pub struct AdminRole {
    #[primary_key]
    #[auto_inc]
    pub role_id: u64,
    #[unique]
    pub player_id: String,
    pub role: AdminRoleLevel,
    pub granted_by: String,
    pub granted_at: Timestamp,
}

AdminRoleLevel Enum

#[derive(SpacetimeType, Clone, Debug, PartialEq)]
pub enum AdminRoleLevel {
    Owner,
    Admin,
    Moderator,
}
Level Description Typical Permissions
Owner Platform owner / super-administrator All operations; can grant/revoke any role
Admin Full administrator Bans, kicks, config changes, whitelist management
Moderator Limited administrator Kicks, temporary bans, view-only for some settings

Usage Patterns

Authorization in Panel Reducers

Panel wrapper reducers check the caller's admin role before delegating to the underlying admin reducer. This provides a second authorization layer beyond the is_gateway() guard:

// Pseudocode — panel reducer authorization
#[spacetimedb::reducer]
pub fn panel_ban_player(ctx: &ReducerContext, player_id: String, reason: String) {
    if !is_gateway(ctx) {
        return;
    }

    // Look up the panel session to identify the acting admin
    let session = PanelSession::filter_by_player_id(&actor_player_id)
        .expect("Panel session must exist");

    // Check role level
    let role = AdminRole::filter_by_player_id(&session.player_id);
    match role {
        Some(r) if r.role == AdminRoleLevel::Admin || r.role == AdminRoleLevel::Owner => {
            // Authorized — proceed with ban
            admin_ban(ctx, player_id, reason);
        }
        _ => {
            log::warn!("Insufficient permissions for panel_ban_player");
        }
    }
}

Unique Constraint

The unique constraint on player_id ensures each player has at most one role assignment. To change a player's role, the existing row must be updated — not a second row inserted. This prevents conflicting role assignments.

Role Hierarchy

The AdminRoleLevel enum defines an implicit hierarchy: Owner > Admin > Moderator. The platform does not enforce this hierarchy through database constraints — it is enforced in reducer logic. For example, a Moderator cannot grant Admin roles, and an Admin cannot revoke an Owner role.

Game Hook Usage

Game hooks subscribe to the AdminRole table to identify in-game administrators. This enables game-specific features like admin commands, spectator mode access, or debug tools based on the player's platform-level role.


Reducer Operation
admin_grant_role Inserts or updates an AdminRole row
admin_revoke_role Deletes the AdminRole row for a player

Subscriber Query Purpose
Game Hook SELECT * FROM admin_role In-game admin identification and permissions
Admin Panel SELECT * FROM admin_role Role management UI, permission checks

For full subscription architecture, see Subscriptions.