From c61a76fa69eb8331676e1fb6d828459db30d2109 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:50:08 +0300 Subject: [PATCH] Merge pull request #1004 from crystallpunk-14/ed-11-03-2025-discord-auth Discord Auth expanse --- .../_CP14/Discord/CP14DiscordAuthManager.cs | 2 + .../_CP14/Discord/DiscordAuthGui.xaml.cs | 6 + .../_CP14/Discord/DiscordAuthManager.cs | 108 ++++++++++++++++-- .../_CP14/Discord/MsgDiscordAuthRequired.cs | 3 + 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/Content.Client/_CP14/Discord/CP14DiscordAuthManager.cs b/Content.Client/_CP14/Discord/CP14DiscordAuthManager.cs index a627ffe2e4..b4bff714a7 100644 --- a/Content.Client/_CP14/Discord/CP14DiscordAuthManager.cs +++ b/Content.Client/_CP14/Discord/CP14DiscordAuthManager.cs @@ -10,6 +10,7 @@ public sealed class DiscordAuthManager [Dependency] private readonly IStateManager _stateManager = default!; public string AuthUrl { get; private set; } = ""; + public string ErrorMessage { get; private set; } = ""; public void Initialize() { @@ -22,6 +23,7 @@ public sealed class DiscordAuthManager if (_stateManager.CurrentState is DiscordAuthState) return; AuthUrl = msg.AuthUrl; + ErrorMessage = msg.ErrorMessage; _stateManager.RequestStateChange(); } } diff --git a/Content.Client/_CP14/Discord/DiscordAuthGui.xaml.cs b/Content.Client/_CP14/Discord/DiscordAuthGui.xaml.cs index 88e75694c2..fff3ce93ff 100644 --- a/Content.Client/_CP14/Discord/DiscordAuthGui.xaml.cs +++ b/Content.Client/_CP14/Discord/DiscordAuthGui.xaml.cs @@ -21,6 +21,7 @@ public sealed partial class DiscordAuthGui : Control LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); var link = _discordAuthManager.AuthUrl; + var errorMessage = _discordAuthManager.ErrorMessage; AuthLinkEdit.SetText(link); DLinkEdit.SetText(DiscordLink); @@ -42,5 +43,10 @@ public sealed partial class DiscordAuthGui : Control { uriOpener.OpenUri(DiscordLink); }; + + if (errorMessage != "") + { + InfoLabel.Text = errorMessage; + } } } diff --git a/Content.Server/_CP14/Discord/DiscordAuthManager.cs b/Content.Server/_CP14/Discord/DiscordAuthManager.cs index 52ab1e5a1f..4ae44b8dbc 100644 --- a/Content.Server/_CP14/Discord/DiscordAuthManager.cs +++ b/Content.Server/_CP14/Discord/DiscordAuthManager.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -5,6 +6,7 @@ using System.Net.Http.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Content.Server.Database; using Content.Shared._CP14.Discord; using Content.Shared.CCVar; using Robust.Server.Player; @@ -12,7 +14,6 @@ using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Network; using Robust.Shared.Player; -using Timer = Robust.Shared.Timing.Timer; namespace Content.Server._CP14.Discord; @@ -21,13 +22,28 @@ public sealed class DiscordAuthManager [Dependency] private readonly IServerNetManager _netMgr = default!; [Dependency] private readonly IPlayerManager _playerMgr = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IServerDbManager _db = default!; private ISawmill _sawmill = default!; private readonly HttpClient _httpClient = new(); - private bool _enabled = false; + private bool _enabled; private string _apiUrl = string.Empty; private string _apiKey = string.Empty; + private string _discordGuild = "1221923073759121468"; //CrystallEdge server required + + private HashSet _blockedGuilds = new() + { + "1361786483073093673", //Testing one + "1346922008000204891", + "1186566619858731038", + "1355279097906855968", + "1352009516941705216", + "1359476387190145034", + "1294276016117911594", + "1278755078315970620", + }; + public event EventHandler? PlayerVerified; public void Initialize() @@ -47,7 +63,7 @@ public sealed class DiscordAuthManager private async void OnAuthCheck(MsgDiscordAuthCheck msg) { var verified = await IsVerified(msg.MsgChannel.UserId); - if (!verified) + if (!verified.Verified) return; var session = _playerMgr.GetSessionById(msg.MsgChannel.UserId); @@ -68,7 +84,7 @@ public sealed class DiscordAuthManager if (args.NewStatus == SessionStatus.Connected) { var verified = await IsVerified(args.Session.UserId); - if (verified) + if (verified.Verified) { PlayerVerified?.Invoke(this, args.Session); return; @@ -76,11 +92,12 @@ public sealed class DiscordAuthManager var message = new MsgDiscordAuthRequired(); message.AuthUrl = await GenerateLink(args.Session.UserId) ?? string.Empty; + message.ErrorMessage = verified.ErrorMessage; args.Session.Channel.SendMessage(message); } } - public async Task IsVerified(NetUserId userId, CancellationToken cancel = default) + public async Task IsVerified(NetUserId userId, CancellationToken cancel = default) { _sawmill.Debug($"Player {userId} check Discord verification"); @@ -93,8 +110,64 @@ public sealed class DiscordAuthManager var response = await _httpClient.SendAsync(request, cancel); _sawmill.Debug($"{await response.Content.ReadAsStringAsync(cancel)}"); - _sawmill.Debug($"{(int) response.StatusCode}"); - return response.StatusCode == HttpStatusCode.OK; + _sawmill.Debug($"{(int)response.StatusCode}"); + var verified = response.StatusCode == HttpStatusCode.OK; + var guildsVerified = await CheckGuilds(userId, cancel); + + if (!verified) + return new AuthData { Verified = false, ErrorMessage = Loc.GetString("cp14-discord-info")}; + + return guildsVerified; + } + + private async Task CheckGuilds(NetUserId userId, CancellationToken cancel = default) + { + var isWhitelisted = await _db.GetWhitelistStatusAsync(userId); + if (isWhitelisted) + { + return new AuthData { Verified = true }; + } + + _sawmill.Debug($"Checking guilds for {userId}"); + + var requestUrl = $"{_apiUrl}/api/guilds?method=uid&id={userId}"; + _sawmill.Debug($"Guilds request url:{requestUrl}"); + var request = new HttpRequestMessage(HttpMethod.Get, requestUrl); + + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + + var response = await _httpClient.SendAsync(request, cancel); + _sawmill.Debug($"Guilds response: {await response.Content.ReadAsStringAsync(cancel)}"); + _sawmill.Debug($"(int) response.StatusCode: {(int)response.StatusCode}"); + if (!response.IsSuccessStatusCode) + { + _sawmill.Debug($"Player {userId} guilds check failed: !response.IsSuccessStatusCode"); + return new AuthData { Verified = false, ErrorMessage = "Unexpected error: !response.IsSuccessStatusCode" }; + } + + var guilds = await response.Content.ReadFromJsonAsync(cancel); + if (guilds is null) + { + _sawmill.Debug($"Player {userId} guilds check failed: guilds is null"); + return new AuthData { Verified = false, ErrorMessage = "Unexpected error: guilds is null" }; + } + + foreach (var guild in guilds.Guilds) + { + if (_blockedGuilds.Contains(guild.Id)) + { + var errorMessage = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String("RXJyb3IgMjcwMQ==")); + return new AuthData { Verified = false, ErrorMessage = errorMessage }; + } + } + + if (guilds.Guilds.All(guild => guild.Id != _discordGuild)) + { + _sawmill.Debug($"Player {userId} is not in required guild {_discordGuild}"); + return new AuthData { Verified = false, ErrorMessage = "You are not a member of the CrystallEdge server." }; + } + + return new AuthData { Verified = true }; } public async Task GenerateLink(NetUserId userId, CancellationToken cancel = default) @@ -119,7 +192,8 @@ public sealed class DiscordAuthManager } catch (Exception e) { - _sawmill.Error($"Unexpected error verifying user via auth service. Error: {e.Message}. Stack: \n{e.StackTrace}"); + _sawmill.Error( + $"Unexpected error verifying user via auth service. Error: {e.Message}. Stack: \n{e.StackTrace}"); return null; } } @@ -129,4 +203,22 @@ public sealed class DiscordAuthManager [JsonPropertyName("link")] public string Link { get; set; } = string.Empty; } + + private sealed class DiscordGuildsResponse + { + [JsonPropertyName("guilds")] + public DiscordGuild[] Guilds { get; set; } = []; + } + + private sealed class DiscordGuild + { + [JsonPropertyName("id")] + public string Id { get; set; } = null!; + } + + public sealed class AuthData + { + public bool Verified { get; set; } + public string ErrorMessage { get; set; } = string.Empty; + } } diff --git a/Content.Shared/_CP14/Discord/MsgDiscordAuthRequired.cs b/Content.Shared/_CP14/Discord/MsgDiscordAuthRequired.cs index e65099881f..1f6fd0dc5a 100644 --- a/Content.Shared/_CP14/Discord/MsgDiscordAuthRequired.cs +++ b/Content.Shared/_CP14/Discord/MsgDiscordAuthRequired.cs @@ -8,14 +8,17 @@ public sealed class MsgDiscordAuthRequired : NetMessage { public override MsgGroups MsgGroup => MsgGroups.Command; public string AuthUrl { get; set; } = string.Empty; + public string ErrorMessage { get; set; } = string.Empty; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { + ErrorMessage = buffer.ReadString(); AuthUrl = buffer.ReadString(); } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { + buffer.Write(ErrorMessage); buffer.Write(AuthUrl); } }