Files
Radicade/Server/Admin/AdminProfileAPI.cs
T
2026-10-16 23:33:13 -07:00

478 lines
14 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Cors;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using RadicadeServer.API.Notifications;
using RadicadeServer.Auth;
using RadicadeServer.DB;
using RadicadeServer.Extensions;
using RadicadeServer.Radicade.Discord;
using RadicadeServer.RecNet.Accounts;
using RadicadeServer.RecNet.Auth;
using RadicadeServer.RecNet.Enums.Messages;
using RadicadeServer.RecNet.Enums.Moderation;
using RadicadeServer.RecNet.Enums.Players;
using RadicadeServer.RecNet.Messages;
using RadicadeServer.RecNet.Moderation;
using RadicadeServer.RecNet.Notifications;
using RadicadeServer.RecNet.Rooms;
using RadicadeServer.Utils;
namespace RadicadeServer.Admin
{
[Route("admin/profiles")]
[EnableCors("AdminPanel")]
public class AdminProfileAPI(RecDBContext db) : Controller
{
private Task<IActionResult?> RejectInvalidKey(string key)
{
return Task.FromResult<IActionResult?>(key.IsAdminKey() ? null : Unauthorized("Invalid secret"));
}
private async Task NotifyUser(long playerId)
{
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == playerId);
if (profile == null)
return;
var plrData = await db.Players.FirstOrDefaultAsync(x => x.AccountId == playerId);
if (plrData == null)
return;
await Notify.SendNotify(playerId, new Notification
{
Id = "AccountUpdate",
Msg = profile
});
await Notify.UpdateSubscriptions(playerId, Notifs.SubscriptionUpdateProfile, profile);
await Notify.SendNotify(playerId, new Notification
{
Id = "SelfAccountUpdate",
Msg = new SelfAccount
{
juniorState = plrData.Birthday != null && plrData.Birthday.Value.Year > 1900
? JuniorState.BasedOnBirthday : JuniorState.PendingOverride,
accountId = playerId,
parentAccountId = plrData.Parent,
availableUsernameChanges = 99,
bio = profile.bio,
displayName = profile.displayName,
email = plrData.Email,
createdAt = profile.createdAt,
isJunior = profile.isJunior,
phone = plrData.Phone,
platforms = profile.platforms,
profileImage = profile.profileImage,
username = profile.username
}
});
}
[HttpGet("whitelist")]
public async Task<IActionResult> WhitelistUser([FromQuery] string key, [FromQuery] long playerId)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == playerId);
if (profile == null) return NotFound();
var cachedLogins = await db.CachedLogins.Where(x => x.accountId == playerId).ToListAsync();
string addedIds = "";
List<string> ids = new();
foreach (var cached in cachedLogins)
{
if (cached.platform != Platforms.Steam) continue;
if (!SteamWhitelist.Contains(cached.platformId))
{
SteamWhitelist.Add(cached.platformId);
addedIds += cached.platformId + ",";
ids.Add(cached.platformId);
}
}
addedIds = addedIds.Trim().TrimEnd(',');
return Ok($"Whitelisted @{profile.username}! Added Steam IDs: {addedIds}");
}
[HttpGet("whitelist/toggle")]
public async Task<IActionResult> ToggleWhitelist([FromQuery] string key, [FromQuery] bool enabled)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
SteamWhitelist.Enabled = enabled;
return Ok(enabled
? "Steam whitelist is now ENABLED. Only approved Steam IDs can sign in.\nConfig: " + SteamWhitelist.FilePath
: "Steam whitelist is now DISABLED. Anyone can sign in.");
}
[HttpGet("whitelist/list")]
public async Task<IActionResult> GetWhitelist([FromQuery] string key)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
return Ok($"Whitelist {(SteamWhitelist.Enabled ? "ENABLED" : "disabled")} ({SteamWhitelist.SteamIds.Count} IDs)\nFilename: {SteamWhitelist.FilePath}\n```json\n{new SteamWhitelist.WhitelistFile { Enabled = SteamWhitelist.Enabled, SteamIds = SteamWhitelist.SteamIds.ToList() }.ToJson()}\n```");
}
[HttpGet("whitelist/add")]
public async Task<IActionResult> AddWhitelistId([FromQuery] string key, [FromQuery] string steamId)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
if (string.IsNullOrWhiteSpace(steamId)) return BadRequest("No steamId given.");
var added = SteamWhitelist.Add(steamId);
return Ok(added
? $"Steam ID {steamId} was added to the whitelist."
: $"Steam ID {steamId} is already on the whitelist.");
}
[HttpGet("whitelist/remove")]
public async Task<IActionResult> RemoveWhitelistId([FromQuery] string key, [FromQuery] string steamId)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
if (string.IsNullOrWhiteSpace(steamId)) return BadRequest("No steamId given.");
var removed = SteamWhitelist.Remove(steamId);
return Ok(removed
? $"Steam ID {steamId} was removed from the whitelist."
: $"Steam ID {steamId} is not on the whitelist.");
}
[HttpGet("set/uname")]
public async Task<IActionResult> SetUsername([FromQuery] string key, [FromQuery] long playerId, [FromQuery] string newName)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == playerId);
if (profile == null) return NotFound();
profile.username = newName;
await db.SaveChangesAsync();
await NotifyUser(profile.accountId);
return Ok("Updated profile!\n```json" + profile.ToJson() + "```");
}
[HttpGet("set/dname")]
public async Task<IActionResult> SetDisplayName([FromQuery] string key, [FromQuery] long playerId, [FromQuery] string newName)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == playerId);
if (profile == null) return NotFound();
profile.displayName = newName;
await db.SaveChangesAsync();
await NotifyUser(profile.accountId);
return Ok("Updated profile!\n```json" + profile.ToJson() + "```");
}
[HttpGet("set/image")]
public async Task<IActionResult> SetProfileImage([FromQuery] string key, [FromQuery] long playerId, [FromQuery] string imgName)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == playerId);
if (profile == null) return NotFound();
profile.profileImage = imgName;
await db.SaveChangesAsync();
await NotifyUser(profile.accountId);
return Ok("Updated profile!\n```json" + profile.ToJson() + "```");
}
[HttpGet("profiles/{profileId}")]
public async Task<IActionResult> GetRoom([FromQuery] string key, long profileId)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == profileId);
if (profile == null) return NotFound();
return Ok(profile);
}
[HttpGet("ban")]
public async Task<IActionResult> BanUser([FromQuery] string key, [FromQuery] long userId, [FromQuery] ReportCategory category, [FromQuery] int duration, [FromQuery] string reason)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == userId);
if (profile == null) return NotFound("User does not exist.");
bool didBan = false;
DateTime banExpiry = DateTime.MaxValue;
string appealCode = string.Empty;
var existingModeration = await db.Moderation.FirstOrDefaultAsync(x => x.PlayerId == userId && x.IsBan);
if (existingModeration != null)
{
if (duration > 0)
{
var expireTime = DateTime.UtcNow.AddMinutes(duration);
existingModeration.ExpireDuration = expireTime;
existingModeration.Message = reason;
existingModeration.Category = category;
existingModeration.IsBan = true;
didBan = true;
banExpiry = expireTime;
}
else if (duration == -1)
{
existingModeration.ExpireDuration = DateTime.MaxValue;
existingModeration.Message = reason;
existingModeration.Category = category;
existingModeration.IsBan = true;
didBan = true;
banExpiry = DateTime.MaxValue;
}
else
{
db.Moderation.Remove(existingModeration);
didBan = false;
await Notify.SendNotify(userId, new Notification { Id = NotificationType.RefreshLogin, Msg = profile });
await PlayerUtils.ReleaseModerationHoldAsync(userId);
}
}
else
{
if (duration > 0)
{
var expireTime = DateTime.UtcNow.AddMinutes(duration);
var (newCode, _) = await PlayerUtils.GetAppealForNewBanAsync(db, userId);
var newModeration = new ModerationDB
{
Category = category,
ExpireDuration = expireTime,
IsBan = true,
Message = reason,
PlayerId = userId,
AppealCode = newCode
};
await db.Moderation.AddAsync(newModeration);
appealCode = newCode ?? string.Empty;
didBan = true;
banExpiry = expireTime;
}
else if (duration == -1)
{
var expireTime = DateTime.MaxValue;
var (newCode, _) = await PlayerUtils.GetAppealForNewBanAsync(db, userId);
var newModeration = new ModerationDB
{
Category = category,
ExpireDuration = expireTime,
IsBan = true,
Message = reason,
PlayerId = userId,
AppealCode = newCode
};
await db.Moderation.AddAsync(newModeration);
appealCode = newCode ?? string.Empty;
didBan = true;
banExpiry = expireTime;
}
}
if (didBan)
{
var activeBan = existingModeration ?? await db.Moderation.FirstOrDefaultAsync(x => x.PlayerId == userId && x.IsBan);
if (activeBan != null && string.IsNullOrWhiteSpace(activeBan.AppealCode))
{
var (newCode, _) = await PlayerUtils.GetAppealForNewBanAsync(db, userId);
activeBan.AppealCode = newCode;
appealCode = newCode ?? string.Empty;
}
else if (string.IsNullOrWhiteSpace(appealCode) && activeBan != null)
{
appealCode = activeBan.AppealCode ?? string.Empty;
}
await db.VoteKicks.AddAsync(new VoteKickLog
{
PlayerId = userId,
Reason = reason,
Category = category,
IsForce = true,
CreatedAt = DateTime.UtcNow,
ExpiresAt = banExpiry
});
var rep = await db.Reputations.FirstOrDefaultAsync(x => x.AccountId == userId);
if (rep != null)
rep.ApplyBanPenalty(permanent: duration == -1, cheating: false);
}
await db.SaveChangesAsync();
if (didBan)
{
await AdminActionDiscord.LogBanAsync(db, userId,
bannedBy: "Admin (Profile API)",
causedByReports: false,
reason: reason,
category: category,
expiry: banExpiry,
isModerator: false);
}
if (didBan)
{
var banStart = (await db.Moderation.AsNoTracking().FirstOrDefaultAsync(x => x.PlayerId == userId && x.IsBan))?.CreatedAt;
await PlayerUtils.KickOrPromptAsync(db, userId, PlayerUtils.BuildBlockDetails(true, category, reason, banExpiry, appealCode: appealCode, startedAtUtc: banStart ?? DateTime.UtcNow));
}
if (didBan)
{
return Ok(duration == -1
? $"Successfully banned @{profile.username} permanently!"
: $"Successfully banned @{profile.username} for {duration} minutes!");
}
else
return Ok($"Successfully unbanned @{profile.username}!");
}
[HttpGet("coach")]
public async Task<IActionResult> SendCoachMessage([FromQuery] string key, [FromQuery] long userId, [FromQuery] string message)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == userId);
if (profile == null) return NotFound("User does not exist.");
await Notify.SendMessage(userId, new Message
{
Type = MessageType.CoachMessage,
Data = message,
FromPlayerId = 1
});
return Ok($"Successfully sent coach message to @{profile.username}!");
}
[HttpGet("coach/all")]
public async Task<IActionResult> SendCoachMessageToAll([FromQuery] string key, [FromQuery] string message)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profiles = await db.Profiles.ToListAsync();
long playerCount = 0;
foreach (var profile in profiles)
{
if (profile.accountId == 1) continue;
await Notify.SendMessage(profile.accountId, new Message
{
Type = MessageType.CoachMessage,
Data = message,
FromPlayerId = 1
});
playerCount++;
}
return Ok($"Successfully sent coach message to {playerCount} players!");
}
[HttpGet("forcequit")]
public async Task<IActionResult> ForceQuitPlayer([FromQuery] string key, [FromQuery] long userId)
{
var rejection = await RejectInvalidKey(key);
if (rejection != null) return rejection;
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == userId);
if (profile == null) return NotFound("User does not exist.");
Notify.SendReliably(userId, new Notification { Id = NotificationType.ModerationQuitGame, Msg = "" },
TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(1), Notify.EmptyObjectPayload(NotificationType.ModerationQuitGame));
var presence = await db.Presences.FirstOrDefaultAsync(x => x.playerId == userId);
if (presence != null)
{
presence.isOnline = false;
presence.roomInstance = null;
presence.LoginLock = null;
await db.SaveChangesAsync();
}
return Ok($"Successfully force quit @{profile.username}!");
}
}
}