Skip to content
RustPluginGenerator

How to make a Rust team auto authorization plugin (TC, locks, turrets)

Automatically authorizes a player on every tool cupboard, code lock and auto turret owned by their team when they join a team, and removes that authorization when they leave, are kicked or the team disbands. It uses OnTeamAcceptInvite, OnTeamLeave, OnTeamKick and OnTeamDisbanded.

Rust teams share a chat, map markers and pings, but they do not share building authorization. When a player joins a team, teammates still have to walk to every tool cupboard and code lock and add the new member by hand, and when someone leaves the team their authorization stays behind on every lock they touched. On a wipe-day server or a large group this is the most common source of "why can't I use my own turret" tickets.

This plugin watches team membership events and mirrors them onto owned entities. On accept it authorizes the new member; on leave, kick and disband it removes authorization. It scans tool cupboards (BuildingPrivlidge), code locks (CodeLock) and auto turrets (AutoTurret) whose OwnerID belongs to a current team member. Owners are never stripped from their own entities, and a per-player opt-out is stored in oxide/data.

It runs entirely off hooks, needs no polling, and its only persistent state is the opt-out list.

The plugin code

TeamAutoAuth.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Team Auto Auth", "RustPluginGenerator", "1.0.0")]
    [Description("Authorizes teammates on tool cupboards, code locks and auto turrets when they join a team, and removes them on leave.")]
    public class TeamAutoAuth : RustPlugin
    {
        private const string PermUse = "teamautoauth.use";
        private const string PermAdmin = "teamautoauth.admin";

        private class PluginConfig
        {
            [JsonProperty("Enabled by default")]
            public bool Enabled { get; set; } = true;

            [JsonProperty("Authorize tool cupboards")]
            public bool AuthCupboards { get; set; } = true;

            [JsonProperty("Authorize code locks")]
            public bool AuthLocks { get; set; } = true;

            [JsonProperty("Authorize auto turrets")]
            public bool AuthTurrets { get; set; } = true;
        }

        private class StoredData
        {
            [JsonProperty("Player opt-outs")]
            public HashSet<ulong> OptOut { get; set; } = new HashSet<ulong>();
        }

        private PluginConfig config;
        private StoredData data;
        private readonly HashSet<ulong> syncedTeams = new HashSet<ulong>();

        protected override void LoadDefaultConfig() => config = new PluginConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<PluginConfig>();
                if (config == null) throw new Exception();
            }
            catch
            {
                PrintWarning("Configuration file is invalid, using defaults");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        private void LoadData()
        {
            try
            {
                data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
            }
            catch
            {
                data = null;
            }
            if (data == null) data = new StoredData();
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, data);

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            permission.RegisterPermission(PermAdmin, this);
            LoadData();
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "You do not have permission to use this command.",
                ["OptedOut"] = "Team auto authorization is now off for you.",
                ["OptedIn"] = "Team auto authorization is now on for you.",
                ["StatusOn"] = "Team auto authorization is currently on.",
                ["StatusOff"] = "Team auto authorization is currently off.",
                ["Usage"] = "Usage: /teamauth <on|off|status> or teamauth.sync <on|off>",
                ["Synced"] = "Rescanned {0} team(s) and applied authorization to online members.",
                ["SyncDefault"] = "Server default is now {0}."
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args) =>
            string.Format(lang.GetMessage(key, this, userId), args);

        private bool IsOptedOut(BasePlayer player) =>
            data != null && data.OptOut.Contains(player.userID.Get());

        private bool WantsAutoAuth(BasePlayer player) => config.Enabled ? !IsOptedOut(player) : IsOptedOut(player);

        private object OnTeamAcceptInvite(RelationshipManager.PlayerTeam playerTeam, BasePlayer player)
        {
            if (player == null || playerTeam == null) return null;
            if (!WantsAutoAuth(player)) return null;

            AuthorizePlayer(player, playerTeam);
            syncedTeams.Add(playerTeam.teamID);
            return null;
        }

        private object OnTeamLeave(RelationshipManager.PlayerTeam playerTeam, BasePlayer player)
        {
            if (player == null || playerTeam == null) return null;
            DeauthorizePlayer(player, playerTeam);
            return null;
        }

        private object OnTeamKick(RelationshipManager.PlayerTeam playerTeam, BasePlayer player, ulong uLong)
        {
            if (player == null || playerTeam == null) return null;
            DeauthorizePlayer(player, playerTeam);
            return null;
        }

        private void OnTeamDisbanded(RelationshipManager.PlayerTeam playerTeam)
        {
            if (playerTeam == null) return;
            foreach (ulong id in new List<ulong>(playerTeam.members))
            {
                BasePlayer member = BasePlayer.FindByID(id);
                if (member != null) DeauthorizePlayer(member, playerTeam);
            }
            syncedTeams.Remove(playerTeam.teamID);
        }

        private void AuthorizePlayer(BasePlayer player, RelationshipManager.PlayerTeam team)
        {
            ApplyToEntities(player, team, true);
        }

        private void DeauthorizePlayer(BasePlayer player, RelationshipManager.PlayerTeam team)
        {
            ApplyToEntities(player, team, false);
        }

        private void ApplyToEntities(BasePlayer player, RelationshipManager.PlayerTeam team, bool grant)
        {
            if (player == null || team == null || team.members == null || team.members.Count == 0) return;
            HashSet<ulong> memberIds = new HashSet<ulong>(team.members);

            foreach (BaseNetworkable networkable in BaseNetworkable.serverEntities)
            {
                BaseEntity entity = networkable as BaseEntity;
                if (entity == null || entity.IsDestroyed) continue;
                if (!memberIds.Contains(entity.OwnerID)) continue;

                if (config.AuthCupboards && entity is BuildingPrivlidge privlidge)
                {
                    if (!grant && privlidge.OwnerID == player.userID.Get()) continue;
                    if (!grant) privlidge.authorizedPlayers.Remove(player.userID);
                    else if (!privlidge.IsAuthed(player)) privlidge.authorizedPlayers.Add(player.userID);
                    privlidge.SendNetworkUpdate();
                }
                else if (config.AuthLocks && entity is CodeLock codeLock)
                {
                    if (!grant && codeLock.OwnerID == player.userID.Get()) continue;
                    if (!grant) codeLock.whitelistPlayers.Remove(player.userID);
                    else if (!codeLock.whitelistPlayers.Contains(player.userID)) codeLock.whitelistPlayers.Add(player.userID);
                    codeLock.SendNetworkUpdate();
                }
                else if (config.AuthTurrets && entity is AutoTurret turret)
                {
                    if (!grant && turret.OwnerID == player.userID.Get()) continue;
                    if (!grant) turret.authorizedPlayers.Remove(player.userID);
                    else if (!turret.authorizedPlayers.Contains(player.userID)) turret.authorizedPlayers.Add(player.userID);
                    turret.SendNetworkUpdate();
                }
            }
        }

        [ChatCommand("teamauth")]
        private void CmdTeamAuth(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                SendReply(player, Msg("NoPermission", player.UserIDString));
                return;
            }

            string arg = args != null && args.Length > 0 ? args[0].ToLower() : "status";
            if (arg == "on")
            {
                data.OptOut.Remove(player.userID.Get());
                SaveData();
                SendReply(player, Msg("OptedIn", player.UserIDString));
            }
            else if (arg == "off")
            {
                data.OptOut.Add(player.userID.Get());
                SaveData();
                SendReply(player, Msg("OptedOut", player.UserIDString));
            }
            else if (arg == "status")
            {
                SendReply(player, Msg(WantsAutoAuth(player) ? "StatusOn" : "StatusOff", player.UserIDString));
            }
            else
            {
                SendReply(player, Msg("Usage", player.UserIDString));
            }
        }

        [ConsoleCommand("teamauth.sync")]
        private void CcmdSync(ConsoleSystem.Arg arg)
        {
            if (arg == null) return;
            BasePlayer caller = arg.Player();
            if (caller != null && !permission.UserHasPermission(caller.UserIDString, PermAdmin) && !caller.IsAdmin)
            {
                arg.ReplyWith(Msg("NoPermission", caller.UserIDString));
                return;
            }

            string value = arg.GetString(0, "toggle");
            if (value == "on" || value == "off")
            {
                config.Enabled = value == "on";
                SaveConfig();
                string status = config.Enabled ? "on" : "off";
                if (caller != null) SendReply(caller, Msg("SyncDefault", caller.UserIDString, status));
                else Puts(string.Format("Server default is now {0}.", status));
            }

            ApplyToOnlineMembers();
        }

        private void ApplyToOnlineMembers()
        {
            int teams = 0;
            foreach (BasePlayer player in BasePlayer.activePlayerList)
            {
                if (player == null || player.currentTeam == 0) continue;
                RelationshipManager.PlayerTeam team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
                if (team == null) continue;
                teams++;
                if (WantsAutoAuth(player)) AuthorizePlayer(player, team);
                else DeauthorizePlayer(player, team);
            }
            Puts(Msg("Synced", null, teams));
        }

        private void Unload()
        {
            SaveData();
        }
    }
}

Hooks used: OnTeamAcceptInvite, OnTeamLeave, OnTeamKick, OnTeamDisbanded

Config file

Created automatically on first load at oxide/config/TeamAutoAuth.json.

TeamAutoAuth.json
{
  "Enabled by default": true,
  "Authorize tool cupboards": true,
  "Authorize code locks": true,
  "Authorize auto turrets": true,
  "Player opt-outs": []
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/teamauth <on|off|status>Turns automatic team authorization on or off for yourself, or prints the current state.teamautoauth.use
teamauth.sync <on|off>Sets the server default and reapplies authorization to every online team member.teamautoauth.admin
Permissions
PermissionGrants
teamautoauth.useAllows a player to toggle automatic team authorization for themselves with /teamauth.
teamautoauth.adminAllows an admin to change the server default and rescan teams with teamauth.sync.

How the code works

  1. Record teams that already got a sync

    OnTeamAcceptInvite fires before the player is actually added to playerTeam.members, so the new member is read from the player argument rather than from the team object. The plugin keeps a HashSet<ulong> syncedTeams and adds playerTeam.teamID after a successful authorize. OnTeamDisbanded removes the ID again so a later team that reuses the ID starts clean.

  2. Read the decision from config and data

    WantsAutoAuth combines config.Enabled with the stored Player opt-outs set: when the server default is on, a player is auto-authorized unless their ID is in the set; when the default is off, only players who have opted in (/teamauth on, which removes them from the set) are handled. The set is loaded from oxide/data/TeamAutoAuth.json in Init and written by SaveData on every change and in Unload.

  3. Authorize teammates on accept

    OnTeamAcceptInvite calls AuthorizePlayer(player, playerTeam), which calls ApplyToEntities(..., grant: true). A single pass over BaseNetworkable.serverEntities casts each entity to BaseEntity, skips any whose OwnerID is not in playerTeam.members, and then checks the three families the config switches allow: BuildingPrivlidge, CodeLock and AutoTurret. Each add is guarded by an IsAuthed or Contains check, so repeated calls never duplicate entries.

  4. Deauthorize on leave, kick and disband

    OnTeamLeave and OnTeamKick both call DeauthorizePlayer with the departing player: for a kick that is the kicked member, which is the second parameter, not the kicker. OnTeamDisbanded copies playerTeam.members into a new list and deauthorizes every member still online through BasePlayer.FindByID. Every hook method returns null so default Rust behavior is untouched. Owner IDs are compared before removal so nobody is stripped from an entity they placed themselves.

  5. Gate the chat command behind a permission

    [ChatCommand("teamauth")] checks permission.UserHasPermission against teamautoauth.use and replies from the lang file. Valid arguments are on, off and status; anything else prints the usage string. The only characters the plugin prints come from LoadDefaultMessages, so the whole command surface can be retranslated without touching code.

  6. Reapply the server default from console

    [ConsoleCommand("teamauth.sync")] takes on or off via arg.GetString(0, ...), writes config.Enabled through SaveConfig, then calls ApplyToOnlineMembers, which walks BasePlayer.activePlayerList, resolves RelationshipManager.ServerInstance.FindTeam(player.currentTeam) and authorizes or deauthorizes depending on WantsAutoAuth. It is usable from RCON; there arg.Player() is null and the reply goes to the console log.

Install it on your server

  1. Save TeamAutoAuth.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. Upload it to the plugins folder

    Oxide: oxide/plugins/. Carbon: carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/TeamAutoAuth.json (Carbon: carbon/configs/TeamAutoAuth.json). After editing, reload it:oxide.reload TeamAutoAuth c.reload TeamAutoAuth # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default teamautoauth.use c.grant group default teamautoauth.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Share storage boxes too

    Extends the scan to storage containers and vending machines so teammates can open shared boxes.

    Generate this version
  • Only sync recent joins

    Restricts authorization to players who joined the team within the last N minutes.

    Generate this version
  • Tell players in chat

    Sends a lang message when authorization is granted or revoked, so players understand why locks changed.

    Generate this version

FAQ

Does this use the team system or the cupboard list?

Both. It reads membership from RelationshipManager.PlayerTeam.members, then writes to each entity's own authorization list. Because it only writes on membership events, an admin who removes someone from a cupboard with the hammer is not fighting a background loop; the next join or leave is what triggers the next write.

How do I install it on Oxide or Carbon?

Drop TeamAutoAuth.cs into oxide/plugins (Carbon: carbon/plugins) and let it compile. Grant teamautoauth.use to your default group and teamautoauth.admin to staff with oxide.grant. The config file is created on first load with the feature enabled, so no further setup is needed.

What happens when an owner leaves a team?

The owner is not deauthorized on entities they own, because OwnerID matches their userID. Teammates are still deauthorized from those entities, so leaving a group does not silently hand a base to the old crew. This behaviour is hard-coded and not exposed in the config.

Does it touch entities owned by players outside the team?

No. The scan checks entity.OwnerID against the team's current member list before calling any authorization method, so cupboards, code locks and turrets belonging to other players are never modified.

How do I turn it off for one player?

The player runs /teamauth off and their ID is stored in oxide/data; /teamauth on removes it. Removing the teamautoauth.use permission locks the setting. The server default is flipped with teamauth.sync off in the console or RCON, which also reapplies to online players.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Write a Rust Oxide plugin called TeamAutoAuth that authorizes teammates on tool cupboards, code locks and auto turrets when they join a team and deauthorizes on leave, kick or disband, with config switches, per-player opt-out and two permissions.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize team auto authorization

Customize