From 61cd13a4703a4872acd7795e25686a432857ea2b Mon Sep 17 00:00:00 2001
From: Simon <63975668+Simyon264@users.noreply.github.com>
Date: Thu, 12 Mar 2026 19:40:47 +0100
Subject: [PATCH] Add banning to server api (#43205)
add banning to server api
---
Content.Server/Administration/ServerApi.cs | 80 ++++++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs
index 7a1c6a82060..2f46d2d3272 100644
--- a/Content.Server/Administration/ServerApi.cs
+++ b/Content.Server/Administration/ServerApi.cs
@@ -7,8 +7,10 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server._NF.Administration;
+using Content.Server.Administration.Managers;
using Content.Server.Administration.Systems;
using Content.Server.Administration.Managers; // Frontier
+using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Presets;
using Content.Server.GameTicking.Rules.Components;
@@ -17,6 +19,7 @@ using Content.Server.RoundEnd;
using Content.Shared.Administration.Managers;
using Content.Shared.Administration; // Frontier
using Content.Shared.CCVar;
+using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Prototypes;
using Robust.Server.ServerStatus;
@@ -61,6 +64,9 @@ public sealed partial class ServerApi : IPostInjectInit
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
+ [Dependency] private readonly IPlayerLocator _locator = default!;
+ [Dependency] private readonly IBanManager _bans = default!;
+ [Dependency] private readonly IServerDbManager _db = default!;
private string _token = string.Empty;
private ISawmill _sawmill = default!;
@@ -79,6 +85,7 @@ public sealed partial class ServerApi : IPostInjectInit
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/end", ActionRoundEnd);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/restartnow", ActionRoundRestartNow);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/kick", ActionKick);
+ RegisterActorHandler(HttpMethod.Post, "/admin/actions/ban", ActionBan);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/add_game_rule", ActionAddGameRule);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/end_game_rule", ActionEndGameRule);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/force_preset", ActionForcePreset);
@@ -312,6 +319,70 @@ public sealed partial class ServerApi : IPostInjectInit
});
}
+ ///
+ /// Bans a player.
+ ///
+ private async Task ActionBan(IStatusHandlerContext context, Actor actor)
+ {
+ var body = await ReadJson(context);
+ if (body == null)
+ return;
+
+ await RunOnMainThread(async () =>
+ {
+ var located = await _locator.LookupIdByNameOrIdAsync(body.Guid.ToString());
+
+ if (located == null)
+ {
+ await RespondError(
+ context,
+ ErrorCode.PlayerNotFound,
+ HttpStatusCode.UnprocessableContent,
+ "Player not found");
+ return;
+ }
+
+ var bans = await _db.GetBansAsync(userId: located.UserId,
+ address: null,
+ hwId: null,
+ modernHWIds: located.LastModernHWIds,
+ includeUnbanned: false);
+ if (bans.Count > 0)
+ {
+ await RespondError(
+ context,
+ ErrorCode.PlayerAlreadyBanned,
+ HttpStatusCode.Conflict,
+ "Player is already banned.");
+ return;
+ }
+
+ var reason = body.Reason ?? "No reason supplied";
+ var info = new CreateServerBanInfo(reason);
+
+ info.AddHWId(located.LastHWId);
+ info.AddUser(located.UserId, located.Username);
+ info.WithSeverity(body.Severity);
+ if (body.Minutes != null && body.Minutes != 0)
+ {
+ info.WithMinutes(body.Minutes.Value);
+ }
+
+ info.WithBanningAdmin(new NetUserId(actor.Guid));
+
+ // Add the ip if the user is currently connected.
+ if (_playerManager.TryGetSessionById(new NetUserId(body.Guid), out var player))
+ {
+ info.AddAddress(player.Channel.RemoteEndPoint.Address);
+ }
+
+ _bans.CreateServerBan(info);
+ await RespondOk(context);
+
+ _sawmill.Info($"Banned player {located.Username} ({located.UserId}) for {reason} lasting {body.Minutes ?? 0} minutes by {FormatLogActor(actor)}");
+ });
+ }
+
///
/// Kicks a player.
///
@@ -653,6 +724,14 @@ public sealed partial class ServerApi : IPostInjectInit
public string? Reason { get; init; }
}
+ private sealed class BanActionBody
+ {
+ public required Guid Guid { get; init; }
+ public string? Reason { get; init; }
+ public NoteSeverity Severity { get; init; }
+ public uint? Minutes { get; init; }
+ }
+
private sealed class GameRuleActionBody
{
public required string GameRuleId { get; init; }
@@ -694,6 +773,7 @@ public sealed partial class ServerApi : IPostInjectInit
PlayerNotFound = 4,
GameRuleNotFound = 5,
BadRequest = 6,
+ PlayerAlreadyBanned = 7,
}
#endregion