Rate limit ahelps (#29219)
* Make chat rate limits a general-purpose system. Intending to use this with ahelps next. * Rate limt ahelps Fixes #28762 * Review comments
This commit is contained in:
parent
60bf5fe896
commit
01ecd1d28b
|
|
@ -9,6 +9,7 @@ using Content.Server.Administration.Managers;
|
|||
using Content.Server.Afk;
|
||||
using Content.Server.Discord;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Mind;
|
||||
|
|
@ -27,6 +28,8 @@ namespace Content.Server.Administration.Systems
|
|||
[UsedImplicitly]
|
||||
public sealed partial class BwoinkSystem : SharedBwoinkSystem
|
||||
{
|
||||
private const string RateLimitKey = "AdminHelp";
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
|
@ -35,6 +38,7 @@ namespace Content.Server.Administration.Systems
|
|||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedMindSystem _minds = default!;
|
||||
[Dependency] private readonly IAfkManager _afkManager = default!;
|
||||
[Dependency] private readonly PlayerRateLimitManager _rateLimit = default!;
|
||||
|
||||
[GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")]
|
||||
private static partial Regex DiscordRegex();
|
||||
|
|
@ -80,6 +84,22 @@ namespace Content.Server.Administration.Systems
|
|||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
|
||||
SubscribeNetworkEvent<BwoinkClientTypingUpdated>(OnClientTypingUpdated);
|
||||
|
||||
_rateLimit.Register(
|
||||
RateLimitKey,
|
||||
new RateLimitRegistration
|
||||
{
|
||||
CVarLimitPeriodLength = CCVars.AhelpRateLimitPeriod,
|
||||
CVarLimitCount = CCVars.AhelpRateLimitCount,
|
||||
PlayerLimitedAction = PlayerRateLimitedAction
|
||||
});
|
||||
}
|
||||
|
||||
private void PlayerRateLimitedAction(ICommonSession obj)
|
||||
{
|
||||
RaiseNetworkEvent(
|
||||
new BwoinkTextMessage(obj.UserId, default, Loc.GetString("bwoink-system-rate-limited"), playSound: false),
|
||||
obj.Channel);
|
||||
}
|
||||
|
||||
private void OnOverrideChanged(string obj)
|
||||
|
|
@ -395,6 +415,9 @@ namespace Content.Server.Administration.Systems
|
|||
return;
|
||||
}
|
||||
|
||||
if (_rateLimit.CountAction(eventArgs.SenderSession, RateLimitKey) != RateLimitStatus.Allowed)
|
||||
return;
|
||||
|
||||
var escapedText = FormattedMessage.EscapeText(message.Text);
|
||||
|
||||
string bwoinkText;
|
||||
|
|
|
|||
|
|
@ -1,84 +1,41 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Chat.Managers;
|
||||
|
||||
internal sealed partial class ChatManager
|
||||
{
|
||||
private readonly Dictionary<ICommonSession, RateLimitDatum> _rateLimitData = new();
|
||||
private const string RateLimitKey = "Chat";
|
||||
|
||||
public bool HandleRateLimit(ICommonSession player)
|
||||
private void RegisterRateLimits()
|
||||
{
|
||||
ref var datum = ref CollectionsMarshal.GetValueRefOrAddDefault(_rateLimitData, player, out _);
|
||||
var time = _gameTiming.RealTime;
|
||||
if (datum.CountExpires < time)
|
||||
{
|
||||
// Period expired, reset it.
|
||||
var periodLength = _configurationManager.GetCVar(CCVars.ChatRateLimitPeriod);
|
||||
datum.CountExpires = time + TimeSpan.FromSeconds(periodLength);
|
||||
datum.Count = 0;
|
||||
datum.Announced = false;
|
||||
}
|
||||
|
||||
var maxCount = _configurationManager.GetCVar(CCVars.ChatRateLimitCount);
|
||||
datum.Count += 1;
|
||||
|
||||
if (datum.Count <= maxCount)
|
||||
return true;
|
||||
|
||||
// Breached rate limits, inform admins if configured.
|
||||
if (_configurationManager.GetCVar(CCVars.ChatRateLimitAnnounceAdmins))
|
||||
{
|
||||
if (datum.NextAdminAnnounce < time)
|
||||
_rateLimitManager.Register(RateLimitKey,
|
||||
new RateLimitRegistration
|
||||
{
|
||||
SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name)));
|
||||
var delay = _configurationManager.GetCVar(CCVars.ChatRateLimitAnnounceAdminsDelay);
|
||||
datum.NextAdminAnnounce = time + TimeSpan.FromSeconds(delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (!datum.Announced)
|
||||
{
|
||||
DispatchServerMessage(player, Loc.GetString("chat-manager-rate-limited"), suppressLog: true);
|
||||
_adminLogger.Add(LogType.ChatRateLimited, LogImpact.Medium, $"Player {player} breached chat rate limits");
|
||||
|
||||
datum.Announced = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
CVarLimitPeriodLength = CCVars.ChatRateLimitPeriod,
|
||||
CVarLimitCount = CCVars.ChatRateLimitCount,
|
||||
CVarAdminAnnounceDelay = CCVars.ChatRateLimitAnnounceAdminsDelay,
|
||||
PlayerLimitedAction = RateLimitPlayerLimited,
|
||||
AdminAnnounceAction = RateLimitAlertAdmins,
|
||||
AdminLogType = LogType.ChatRateLimited,
|
||||
});
|
||||
}
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
private void RateLimitPlayerLimited(ICommonSession player)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
_rateLimitData.Remove(e.Session);
|
||||
DispatchServerMessage(player, Loc.GetString("chat-manager-rate-limited"), suppressLog: true);
|
||||
}
|
||||
|
||||
private struct RateLimitDatum
|
||||
private void RateLimitAlertAdmins(ICommonSession player)
|
||||
{
|
||||
/// <summary>
|
||||
/// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) this rate limit period will expire at.
|
||||
/// </summary>
|
||||
public TimeSpan CountExpires;
|
||||
if (_configurationManager.GetCVar(CCVars.ChatRateLimitAnnounceAdmins))
|
||||
SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many messages have been sent in the current rate limit period.
|
||||
/// </summary>
|
||||
public int Count;
|
||||
|
||||
/// <summary>
|
||||
/// Have we announced to the player that they've been blocked in this rate limit period?
|
||||
/// </summary>
|
||||
public bool Announced;
|
||||
|
||||
/// <summary>
|
||||
/// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) of the
|
||||
/// next time we can send an announcement to admins about rate limit breach.
|
||||
/// </summary>
|
||||
public TimeSpan NextAdminAnnounce;
|
||||
public RateLimitStatus HandleRateLimit(ICommonSession player)
|
||||
{
|
||||
return _rateLimitManager.CountAction(player, RateLimitKey);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,17 @@ using Content.Server.Administration.Logs;
|
|||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.MoMMI;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Chat.Managers
|
||||
|
|
@ -43,8 +42,7 @@ namespace Content.Server.Chat.Managers
|
|||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _netConfigManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly PlayerRateLimitManager _rateLimitManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum length a player-sent message can be sent
|
||||
|
|
@ -64,7 +62,7 @@ namespace Content.Server.Chat.Managers
|
|||
_configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true);
|
||||
_configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true);
|
||||
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
RegisterRateLimits();
|
||||
}
|
||||
|
||||
private void OnOocEnabledChanged(bool val)
|
||||
|
|
@ -206,7 +204,7 @@ namespace Content.Server.Chat.Managers
|
|||
/// <param name="type">The type of message.</param>
|
||||
public void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type)
|
||||
{
|
||||
if (!HandleRateLimit(player))
|
||||
if (HandleRateLimit(player) != RateLimitStatus.Allowed)
|
||||
return;
|
||||
|
||||
// Check if message exceeds the character limit
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Robust.Shared.Network;
|
||||
|
|
@ -50,6 +52,6 @@ namespace Content.Server.Chat.Managers
|
|||
/// </summary>
|
||||
/// <param name="player">The player sending a chat message.</param>
|
||||
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
|
||||
bool HandleRateLimit(ICommonSession player);
|
||||
RateLimitStatus HandleRateLimit(ICommonSession player);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Content.Server.Administration.Managers;
|
|||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Examine;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Server.Nyanotrasen.Chat;
|
||||
|
|
@ -189,7 +190,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
return;
|
||||
}
|
||||
|
||||
if (player != null && !_chatManager.HandleRateLimit(player))
|
||||
if (player != null && _chatManager.HandleRateLimit(player) != RateLimitStatus.Allowed)
|
||||
return;
|
||||
|
||||
// Sus
|
||||
|
|
@ -282,7 +283,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
if (!CanSendInGame(message, shell, player))
|
||||
return;
|
||||
|
||||
if (player != null && !_chatManager.HandleRateLimit(player))
|
||||
if (player != null && _chatManager.HandleRateLimit(player) != RateLimitStatus.Allowed)
|
||||
return;
|
||||
|
||||
// It doesn't make any sense for a non-player to send in-game OOC messages, whereas non-players may be sending
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ using Content.Server.Info;
|
|||
using Content.Server.IoC;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Players.JobWhitelist;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Server.ServerInfo;
|
||||
using Content.Server.ServerUpdates;
|
||||
|
|
@ -109,6 +111,7 @@ namespace Content.Server.Entry
|
|||
_updateManager.Initialize();
|
||||
_playTimeTracking.Initialize();
|
||||
IoCManager.Resolve<JobWhitelistManager>().Initialize();
|
||||
IoCManager.Resolve<PlayerRateLimitManager>().Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ using Content.Server.Info;
|
|||
using Content.Server.Maps;
|
||||
using Content.Server.MoMMI;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Players.JobWhitelist;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Server.ServerInfo;
|
||||
using Content.Server.ServerUpdates;
|
||||
|
|
@ -63,6 +65,7 @@ namespace Content.Server.IoC
|
|||
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
||||
IoCManager.Register<ServerApi>();
|
||||
IoCManager.Register<JobWhitelistManager>();
|
||||
IoCManager.Register<PlayerRateLimitManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Players.RateLimiting;
|
||||
|
||||
/// <summary>
|
||||
/// General-purpose system to rate limit actions taken by clients, such as chat messages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Different categories of rate limits must be registered ahead of time by calling <see cref="Register"/>.
|
||||
/// Once registered, you can simply call <see cref="CountAction"/> to count a rate-limited action for a player.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This system is intended for rate limiting player actions over short periods,
|
||||
/// to ward against spam that can cause technical issues such as admin client load.
|
||||
/// It should not be used for in-game actions or similar.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Rate limits are reset when a client reconnects.
|
||||
/// This should not be an issue for the reasonably short rate limit periods this system is intended for.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="RateLimitRegistration"/>
|
||||
public sealed class PlayerRateLimitManager
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private readonly Dictionary<string, RegistrationData> _registrations = new();
|
||||
private readonly Dictionary<ICommonSession, Dictionary<string, RateLimitDatum>> _rateLimitData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Count and validate an action performed by a player against rate limits.
|
||||
/// </summary>
|
||||
/// <param name="player">The player performing the action.</param>
|
||||
/// <param name="key">The key string that was previously used to register a rate limit category.</param>
|
||||
/// <returns>Whether the action counted should be blocked due to surpassing rate limits or not.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="player"/> is not a connected player
|
||||
/// OR <paramref name="key"/> is not a registered rate limit category.
|
||||
/// </exception>
|
||||
/// <seealso cref="Register"/>
|
||||
public RateLimitStatus CountAction(ICommonSession player, string key)
|
||||
{
|
||||
if (player.Status == SessionStatus.Disconnected)
|
||||
throw new ArgumentException("Player is not connected");
|
||||
if (!_registrations.TryGetValue(key, out var registration))
|
||||
throw new ArgumentException($"Unregistered key: {key}");
|
||||
|
||||
var playerData = _rateLimitData.GetOrNew(player);
|
||||
ref var datum = ref CollectionsMarshal.GetValueRefOrAddDefault(playerData, key, out _);
|
||||
var time = _gameTiming.RealTime;
|
||||
if (datum.CountExpires < time)
|
||||
{
|
||||
// Period expired, reset it.
|
||||
datum.CountExpires = time + registration.LimitPeriod;
|
||||
datum.Count = 0;
|
||||
datum.Announced = false;
|
||||
}
|
||||
|
||||
datum.Count += 1;
|
||||
|
||||
if (datum.Count <= registration.LimitCount)
|
||||
return RateLimitStatus.Allowed;
|
||||
|
||||
// Breached rate limits, inform admins if configured.
|
||||
if (registration.AdminAnnounceDelay is { } cvarAnnounceDelay)
|
||||
{
|
||||
if (datum.NextAdminAnnounce < time)
|
||||
{
|
||||
registration.Registration.AdminAnnounceAction!(player);
|
||||
datum.NextAdminAnnounce = time + cvarAnnounceDelay;
|
||||
}
|
||||
}
|
||||
|
||||
if (!datum.Announced)
|
||||
{
|
||||
registration.Registration.PlayerLimitedAction(player);
|
||||
_adminLog.Add(
|
||||
registration.Registration.AdminLogType,
|
||||
LogImpact.Medium,
|
||||
$"Player {player} breached '{key}' rate limit ");
|
||||
|
||||
datum.Announced = true;
|
||||
}
|
||||
|
||||
return RateLimitStatus.Blocked;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new rate limit category.
|
||||
/// </summary>
|
||||
/// <param name="key">
|
||||
/// The key string that will be referred to later with <see cref="CountAction"/>.
|
||||
/// Must be unique and should probably just be a constant somewhere.
|
||||
/// </param>
|
||||
/// <param name="registration">The data specifying the rate limit's parameters.</param>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="key"/> has already been registered.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="registration"/> is invalid.</exception>
|
||||
public void Register(string key, RateLimitRegistration registration)
|
||||
{
|
||||
if (_registrations.ContainsKey(key))
|
||||
throw new InvalidOperationException($"Key already registered: {key}");
|
||||
|
||||
var data = new RegistrationData
|
||||
{
|
||||
Registration = registration,
|
||||
};
|
||||
|
||||
if ((registration.AdminAnnounceAction == null) != (registration.CVarAdminAnnounceDelay == null))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Must set either both {nameof(registration.AdminAnnounceAction)} and {nameof(registration.CVarAdminAnnounceDelay)} or neither");
|
||||
}
|
||||
|
||||
_cfg.OnValueChanged(
|
||||
registration.CVarLimitCount,
|
||||
i => data.LimitCount = i,
|
||||
invokeImmediately: true);
|
||||
_cfg.OnValueChanged(
|
||||
registration.CVarLimitPeriodLength,
|
||||
i => data.LimitPeriod = TimeSpan.FromSeconds(i),
|
||||
invokeImmediately: true);
|
||||
|
||||
if (registration.CVarAdminAnnounceDelay != null)
|
||||
{
|
||||
_cfg.OnValueChanged(
|
||||
registration.CVarLimitCount,
|
||||
i => data.AdminAnnounceDelay = TimeSpan.FromSeconds(i),
|
||||
invokeImmediately: true);
|
||||
}
|
||||
|
||||
_registrations.Add(key, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the manager's functionality at game startup.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
_rateLimitData.Remove(e.Session);
|
||||
}
|
||||
|
||||
private sealed class RegistrationData
|
||||
{
|
||||
public required RateLimitRegistration Registration { get; init; }
|
||||
public TimeSpan LimitPeriod { get; set; }
|
||||
public int LimitCount { get; set; }
|
||||
public TimeSpan? AdminAnnounceDelay { get; set; }
|
||||
}
|
||||
|
||||
private struct RateLimitDatum
|
||||
{
|
||||
/// <summary>
|
||||
/// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) this rate limit period will expire at.
|
||||
/// </summary>
|
||||
public TimeSpan CountExpires;
|
||||
|
||||
/// <summary>
|
||||
/// How many actions have been done in the current rate limit period.
|
||||
/// </summary>
|
||||
public int Count;
|
||||
|
||||
/// <summary>
|
||||
/// Have we announced to the player that they've been blocked in this rate limit period?
|
||||
/// </summary>
|
||||
public bool Announced;
|
||||
|
||||
/// <summary>
|
||||
/// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) of the
|
||||
/// next time we can send an announcement to admins about rate limit breach.
|
||||
/// </summary>
|
||||
public TimeSpan NextAdminAnnounce;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all data necessary to register a rate limit with <see cref="PlayerRateLimitManager.Register"/>.
|
||||
/// </summary>
|
||||
public sealed class RateLimitRegistration
|
||||
{
|
||||
/// <summary>
|
||||
/// CVar that controls the period over which the rate limit is counted, measured in seconds.
|
||||
/// </summary>
|
||||
public required CVarDef<int> CVarLimitPeriodLength { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CVar that controls how many actions are allowed in a single rate limit period.
|
||||
/// </summary>
|
||||
public required CVarDef<int> CVarLimitCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// An action that gets invoked when this rate limit has been breached by a player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used for informing players or taking administrative action.
|
||||
/// </remarks>
|
||||
public required Action<ICommonSession> PlayerLimitedAction { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CVar that controls the minimum delay between admin notifications, measured in seconds.
|
||||
/// This can be omitted to have no admin notification system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set, <see cref="AdminAnnounceAction"/> must be set too.
|
||||
/// </remarks>
|
||||
public CVarDef<int>? CVarAdminAnnounceDelay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// An action that gets invoked when a rate limit was breached and admins should be notified.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set, <see cref="CVarAdminAnnounceDelay"/> must be set too.
|
||||
/// </remarks>
|
||||
public Action<ICommonSession>? AdminAnnounceAction { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Log type used to log rate limit violations to the admin logs system.
|
||||
/// </summary>
|
||||
public LogType AdminLogType { get; init; } = LogType.RateLimited;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a rate-limited operation.
|
||||
/// </summary>
|
||||
/// <seealso cref="PlayerRateLimitManager.CountAction"/>
|
||||
public enum RateLimitStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The action was not blocked by the rate limit.
|
||||
/// </summary>
|
||||
Allowed,
|
||||
|
||||
/// <summary>
|
||||
/// The action was blocked by the rate limit.
|
||||
/// </summary>
|
||||
Blocked,
|
||||
}
|
||||
|
|
@ -98,5 +98,13 @@ public enum LogType
|
|||
ChatRateLimited = 87,
|
||||
AtmosTemperatureChanged = 88,
|
||||
DeviceNetwork = 89,
|
||||
StoreRefund = 90
|
||||
StoreRefund = 90,
|
||||
|
||||
/// <summary>
|
||||
/// User was rate-limited for some spam action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a default value used by <c>PlayerRateLimitManager</c>, though users can use different log types.
|
||||
/// </remarks>
|
||||
RateLimited = 91,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -871,6 +871,25 @@ namespace Content.Shared.CCVar
|
|||
public static readonly CVarDef<bool> AdminBypassMaxPlayers =
|
||||
CVarDef.Create("admin.bypass_max_players", true, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* AHELP
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Ahelp rate limit values are accounted in periods of this size (seconds).
|
||||
/// After the period has passed, the count resets.
|
||||
/// </summary>
|
||||
/// <seealso cref="AhelpRateLimitCount"/>
|
||||
public static readonly CVarDef<int> AhelpRateLimitPeriod =
|
||||
CVarDef.Create("ahelp.rate_limit_period", 2, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// How many ahelp messages are allowed in a single rate limit period.
|
||||
/// </summary>
|
||||
/// <seealso cref="AhelpRateLimitPeriod"/>
|
||||
public static readonly CVarDef<int> AhelpRateLimitCount =
|
||||
CVarDef.Create("ahelp.rate_limit_count", 10, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Explosions
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,3 +14,5 @@ bwoink-system-typing-indicator = {$players} {$count ->
|
|||
admin-bwoink-play-sound = Bwoink?
|
||||
|
||||
bwoink-title-none-selected = None selected
|
||||
|
||||
bwoink-system-rate-limited = System: you are sending messages too quickly.
|
||||
|
|
|
|||
Loading…
Reference in New Issue