using System; using System.Text.Json; using BestHTTP; using Il2CppInterop.Runtime; internal static class HubSocketAuth { internal static string Token { get; private set; } internal static string LastLoginRefusalCode { get; private set; } private static readonly string[] AuthorizationHeaders = { "Authorization", "authorization", "AUTHORIZATION" }; private static readonly System.Collections.Generic.HashSet Stated = new(); internal static bool IsServer(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out var request) || !Uri.TryCreate(RadicadeEacBypassPlugin.ServerUrl.Value, UriKind.Absolute, out var server)) return false; return request.Host.Equals(server.Host, StringComparison.OrdinalIgnoreCase) && request.Port == server.Port && (request.Scheme == server.Scheme || request.Scheme == (server.Scheme == "https" ? "wss" : "ws")); } internal static void Capture(HTTPRequest request) { if (request?.Uri == null || !IsServer(request.Uri.ToString())) return; foreach (var name in AuthorizationHeaders) { var values = request.GetHeaderValues(name); if (values == null || values.Count == 0) continue; var value = values[0]; if (value != null && value.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) Token = value.Substring(7).Trim(); } } internal static void CaptureFromLoginResponse(HTTPRequest request, string url) { if (!IsServer(url) || !new Uri(url).AbsolutePath.EndsWith("/connect/token", StringComparison.OrdinalIgnoreCase)) return; Token = null; var original = request.Callback; request.Callback = DelegateSupport.ConvertDelegate( new Action((req, response) => { try { if (response != null && response.StatusCode == 200) { using var document = JsonDocument.Parse(response.DataAsText); if (document.RootElement.TryGetProperty("access_token", out var token) && token.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(token.GetString())) { Token = token.GetString(); LastLoginRefusalCode = null; Diag.Trace("[Realtime] Login token captured (value omitted)"); } else { Token = null; var error = document.RootElement.TryGetProperty("error", out var errorElement) ? errorElement.GetString() : null; var description = document.RootElement.TryGetProperty("error_description", out var descriptionElement) ? descriptionElement.GetString() : null; LastLoginRefusalCode = error; RadicadeEacBypassPlugin.LogSource.LogWarning( "[Realtime] connect/token answered HTTP 200 without an access_token - that is a login REFUSAL, not a login. " + "error='" + (error ?? "-") + "', error_description='" + (description ?? "-") + "'."); } } } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning("[Realtime] Token parse failed: " + ex.GetType().Name); } finally { original?.Invoke(req, response); } })); } internal static void AuthenticateHubRequest(HTTPRequest request, string url) { if (!IsServer(url) || !NameserverRedirect.IsHubUrl(url)) return; if (!string.IsNullOrWhiteSpace(Token)) { request.Uri = new Il2CppSystem.Uri(NameserverRedirect.EnsureHubToken(url, Token)); request.SetHeader("Authorization", "Bearer " + Token); } Diag.Trace("[Realtime] Hub request " + new Uri(url).AbsolutePath + "; token=" + (NameserverRedirect.HasHubToken(request.Uri.ToString()) ? "present" : "missing")); } internal static void AuthenticateServerRequest(HTTPRequest request, string url) { if (request == null || !IsServer(url) || string.IsNullOrWhiteSpace(Token)) return; try { if (IsAnonymousPath(new Uri(url).AbsolutePath)) return; foreach (var name in AuthorizationHeaders) { var values = request.GetHeaderValues(name); if (values != null && values.Count > 0) return; } request.SetHeader("Authorization", "Bearer " + Token); var path = new Uri(url).AbsolutePath; if (Stated.Add(path)) Diag.Trace("[Auth] stated the session token on " + path + " (the client sent none)"); } catch (Exception ex) { Diag.Warn("[Auth] could not state the token on " + url + ": " + ex.GetType().Name); } } private static bool IsAnonymousPath(string path) => path.StartsWith("/auth/connect/token", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/auth/cachedlogin", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/auth/eac/", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/images/", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/cdn/", StringComparison.OrdinalIgnoreCase); }