diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs
index f1f09d4b50..1990de8a6f 100644
--- a/Content.Server/Administration/ServerApi.cs
+++ b/Content.Server/Administration/ServerApi.cs
@@ -6,7 +6,9 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
+using Content.Server.Administration.Managers;
using Content.Server.Administration.Systems;
+using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Presets;
using Content.Server.GameTicking.Rules.Components;
@@ -14,6 +16,7 @@ using Content.Server.Maps;
using Content.Server.RoundEnd;
using Content.Shared.Administration.Managers;
using Content.Shared.CCVar;
+using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Prototypes;
using Robust.Server.ServerStatus;
@@ -58,6 +61,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!;
@@ -76,6 +82,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);
@@ -307,6 +314,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.
///
@@ -616,6 +687,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; }
@@ -657,6 +736,7 @@ public sealed partial class ServerApi : IPostInjectInit
PlayerNotFound = 4,
GameRuleNotFound = 5,
BadRequest = 6,
+ PlayerAlreadyBanned = 7,
}
#endregion