yikes,
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Vanadium.Classes;
|
||||
using Vanadium.Classes.DBs;
|
||||
|
||||
namespace Vanadium.Auth
|
||||
{
|
||||
public static class Build2025Validator
|
||||
{
|
||||
private const string GuildId = "1034585922186002432";
|
||||
//private const string boosterRoleId = "1511181684438204457";
|
||||
|
||||
private static readonly HttpClient _http = new();
|
||||
|
||||
public static bool Is2025Build(string? ver)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ver)) return false;
|
||||
string v = ver.Trim();
|
||||
return v == "20250724" || v == "20250724.01";
|
||||
}
|
||||
|
||||
public static async Task<(bool allowed, string? errorKey, string? errorDesc)> ValidateAsync(long playerId)
|
||||
{
|
||||
var player = PlayerDB.Players.FindById(playerId); // i dont care how cluttered ts is bro, it works
|
||||
Console.WriteLine($"Validating 2025 build access for player {playerId} ({player?.Player?.DiscordUserId})");
|
||||
if (player == null)
|
||||
return (false, "invalid_client", "No discord account linked.");
|
||||
Console.WriteLine($"Player found: {player.PlayerId}");
|
||||
|
||||
Console.WriteLine($"PlayerRoles: {(player.PlayerRoles == null ? "null" : $"[{string.Join(", ", player.PlayerRoles)}]")}");
|
||||
if (player.PlayerRoles?.Contains(Classes.DBs.DBClasses.PlayerDBClasses.PlayerRoles.Developer) ?? false)
|
||||
return (true, null, null);
|
||||
Console.WriteLine($"Player is not a developer, checking discord access... Roles: {string.Join(", ", player.PlayerRoles ?? new List<Classes.DBs.DBClasses.PlayerDBClasses.PlayerRoles>())}");
|
||||
if (Controllers.WebsiteController.ReadRRPlusIds().Contains(playerId))
|
||||
return (true, null, null);
|
||||
|
||||
string? discordUserId = player.Player?.DiscordUserId;
|
||||
if (string.IsNullOrWhiteSpace(discordUserId))
|
||||
return (false, "invalid_client", "No discord account linked.");
|
||||
|
||||
bool linked = player.Player?.DiscordLinked ?? false;
|
||||
if (!linked)
|
||||
return (false, "invalid_client", "No discord account linked.");
|
||||
|
||||
string token = ServerConfig.CountBotToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
return (false, "invalid_client", "No access to play 2025 build.");
|
||||
|
||||
bool inGuild = await IsUserInGuildAsync(token, discordUserId);
|
||||
if (!inGuild)
|
||||
return (false, "invalid_client", "No access to play 2025 build.");
|
||||
|
||||
bool hasAccess = await UserHasAccessAsync(token, discordUserId);
|
||||
if (!hasAccess)
|
||||
return (false, "invalid_client", "No access to play 2025 build.");
|
||||
|
||||
return (true, null, null);
|
||||
}
|
||||
|
||||
private static async Task<bool> IsUserInGuildAsync(string botToken, string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var req = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
$"https://discord.com/api/v10/guilds/{GuildId}/members/{userId}");
|
||||
req.Headers.Authorization = new AuthenticationHeaderValue("Bot", botToken);
|
||||
|
||||
var resp = await _http.SendAsync(req);
|
||||
return resp.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> UserHasAccessAsync(string botToken, string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var req = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
$"https://discord.com/api/v10/guilds/{GuildId}/members/{userId}");
|
||||
req.Headers.Authorization = new AuthenticationHeaderValue("Bot", botToken);
|
||||
|
||||
var resp = await _http.SendAsync(req);
|
||||
if (!resp.IsSuccessStatusCode) return false;
|
||||
|
||||
string json = await resp.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("premium_since", out var premiumSince) && premiumSince.ValueKind != JsonValueKind.Null) // is null when not a booster
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Vanadium.Classes.DBs;
|
||||
using Vanadium.Classes.DBs.DBClasses;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
|
||||
|
||||
namespace Vanadium.Auth
|
||||
{
|
||||
public static class AuthStuff
|
||||
{
|
||||
private static readonly string Secret = /*Environment.GetEnvironmentVariable("JWT_SECRET") ?? */"asdahuiwefuhawefipoklefjauiegofwkplijhbgyuTiefhugytefui";
|
||||
private static readonly string Issuer = "ajnshuyauhj";
|
||||
private static readonly HttpClient _http = new HttpClient();
|
||||
|
||||
|
||||
public static string Encode(long accountId, string xrnsigKey, string ver)
|
||||
{
|
||||
/*long accountId = accountIdOld; // nhaaahhhhh what is windows xp doing Bro
|
||||
if (accountId == 1453 || accountId == 1533 || accountId == 2250)
|
||||
{
|
||||
accountId = 49;
|
||||
}
|
||||
if (accountId == 1457)
|
||||
{
|
||||
accountId = 1427;
|
||||
}
|
||||
if (accountId == 1293)
|
||||
{
|
||||
accountId = 786;
|
||||
}*/
|
||||
|
||||
if (ver.Contains(".01"))
|
||||
ver = ver.Replace(".01", "");
|
||||
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var player = PlayerDB.Players.FindById(accountId);
|
||||
var roles = new List<string> { "gameClient", "limitsv2" };
|
||||
|
||||
if (player?.PlayerRoles != null)
|
||||
{
|
||||
if (player.PlayerRoles.Contains(PlayerRoles.Developer))
|
||||
roles.Add("developer");
|
||||
if (player.PlayerRoles.Contains(PlayerRoles.Moderator))
|
||||
roles.Add("moderator");
|
||||
if (player.PlayerRoles.Contains(PlayerRoles.Keepsake))
|
||||
roles.Add("keepsake");
|
||||
if (player.PlayerRoles.Contains(PlayerRoles.influencer))
|
||||
roles.Add("influencer");
|
||||
if (player.PlayerRoles.Contains(PlayerRoles.betastudio))
|
||||
roles.Add("betastudio");
|
||||
if (player?.Player?.IsJunior == true)
|
||||
roles.Add("junior");
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim("sub", accountId.ToString()),
|
||||
new Claim("pid", accountId.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
|
||||
new Claim("vanadium.key", xrnsigKey),
|
||||
new Claim("ver", ver)
|
||||
};
|
||||
|
||||
if (roles.Count > 0)
|
||||
claims.Add(new Claim("role", JsonSerializer.Serialize(roles), JsonClaimValueTypes.JsonArray));
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: Issuer,
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddHours(12),
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
public static string GenRefreshToken(long accountId)
|
||||
{
|
||||
var player = PlayerDB.Players.FindById(accountId);
|
||||
if (player == null)
|
||||
return "";
|
||||
|
||||
var refreshToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray())
|
||||
.Replace('+', '-')
|
||||
.Replace('/', '_')
|
||||
.TrimEnd('=');
|
||||
|
||||
player.RefreshToken = refreshToken;
|
||||
player.RefreshTokenExpires = DateTime.UtcNow.AddHours(12);
|
||||
PlayerDB.Players.Update(player);
|
||||
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public static long? ValidateRefreshToken(string refreshToken)
|
||||
{
|
||||
var player = PlayerDB.Players.FindOne(p => p.RefreshToken == refreshToken);
|
||||
if (player == null)
|
||||
return null;
|
||||
|
||||
if (player.RefreshTokenExpires < DateTime.UtcNow)
|
||||
return null;
|
||||
|
||||
return player.PlayerId;
|
||||
}
|
||||
|
||||
public static long? GetPlayerId(HttpRequest request)
|
||||
{
|
||||
var auth = request.Headers["Authorization"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
|
||||
return null;
|
||||
|
||||
var tokenStr = auth.Substring("Bearer ".Length).Trim();
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret));
|
||||
|
||||
handler.ValidateToken(tokenStr, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = key,
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = Issuer,
|
||||
ValidateAudience = false,
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
}, out var validated);
|
||||
|
||||
var jwt = (JwtSecurityToken)validated;
|
||||
|
||||
var sub = jwt.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
|
||||
var pid = jwt.Claims.FirstOrDefault(c => c.Type == "pid")?.Value;
|
||||
|
||||
if (sub == null || pid == null || sub != pid)
|
||||
return null;
|
||||
|
||||
if (!long.TryParse(sub, out var accountId))
|
||||
return null;
|
||||
|
||||
var player = PlayerDB.Players.FindById(accountId);
|
||||
if (player == null)
|
||||
return null;
|
||||
|
||||
return accountId;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetAndEnsureAppVersion(HttpRequest request)
|
||||
{
|
||||
var auth = request.Headers["Authorization"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
|
||||
return null;
|
||||
|
||||
var tokenStr = auth.Substring("Bearer ".Length).Trim();
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwt = handler.ReadJwtToken(tokenStr);
|
||||
var values = jwt.Claims.FirstOrDefault(c => c.Type == "ver")?.Value;
|
||||
var id = jwt.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
|
||||
//Console.WriteLine($"[AuthStuff App Version] App version for player {id}:{values}Type: {values?.GetType()}");
|
||||
var ver = double.TryParse(values, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var validVer) ? validVer : (double?)null;
|
||||
var player = GetCurrentPlayer(request);
|
||||
if (player != null)
|
||||
{
|
||||
player.Player.PlayerExtra.Heartbeat.appVersion = ver.HasValue ? ver.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : Classes.ServerConfig.GameVersion.ToString();
|
||||
PlayerDB.Players.Update(player);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetXRNSIG(HttpRequest request)
|
||||
{
|
||||
var auth = request.Headers["Authorization"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
|
||||
return null;
|
||||
|
||||
var tokenStr = auth.Substring("Bearer ".Length).Trim();
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwt = handler.ReadJwtToken(tokenStr);
|
||||
return jwt.Claims.FirstOrDefault(c => c.Type == "vanadium.key")?.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FullPlayer? GetCurrentPlayer(HttpRequest request)
|
||||
{
|
||||
var id = GetPlayerId(request);
|
||||
return id.HasValue ? PlayerDB.Players.FindById(id.Value) : null;
|
||||
}
|
||||
|
||||
public static FullPlayer? GetOnlinePlayer(HttpRequest request)
|
||||
{
|
||||
var id = GetPlayerId(request);
|
||||
if (!id.HasValue)
|
||||
return null;
|
||||
|
||||
if (!Controllers.NotificationsController.PlayerConnections.TryGetValue(id.Value, out var connections) || connections.Count == 0)
|
||||
return null;
|
||||
|
||||
return PlayerDB.Players.FindById(id.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Vanadium.Classes.DBs;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
|
||||
|
||||
namespace Vanadium.Auth
|
||||
{
|
||||
public static class RNSIGHandler
|
||||
{
|
||||
private static readonly ConcurrentDictionary<long, string> _playerKeys = new();
|
||||
|
||||
public static string IssuePlayerKey(long accountId)
|
||||
{
|
||||
string key = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
||||
_playerKeys[accountId] = key;
|
||||
//Console.WriteLine($"[RNSIG] made a key for player={accountId} key={key}");
|
||||
return key;
|
||||
}
|
||||
public static string IssuePlayerFixedKey(long accountId, string key)
|
||||
{
|
||||
if (!HasPlayerKey(accountId))
|
||||
{
|
||||
_playerKeys[accountId] = key;
|
||||
//Console.WriteLine($"[RNSIG] made a fixed key for player={accountId} key={key}");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public static bool HasPlayerKey(long accountId)
|
||||
{
|
||||
return _playerKeys.ContainsKey(accountId);
|
||||
}
|
||||
|
||||
private static string ComputeSignature(byte[] keyBytes, string uri, byte[] body)
|
||||
{
|
||||
using var hmac = new HMACSHA256(keyBytes);
|
||||
|
||||
hmac.TransformBlock(Encoding.ASCII.GetBytes(uri), 0, Encoding.ASCII.GetByteCount(uri), null, 0);
|
||||
|
||||
if (body != null && body.Length > 0)
|
||||
{
|
||||
byte[] lenBytes = BitConverter.GetBytes((uint)body.Length);
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBytes);
|
||||
hmac.TransformBlock(lenBytes, 0, 4, null, 0);
|
||||
|
||||
if (body.Length > 2048)
|
||||
{
|
||||
int step = body.Length / 16;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int offset = i * step;
|
||||
int count = Math.Min(128, body.Length - offset);
|
||||
hmac.TransformBlock(body, offset, count, null, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hmac.TransformBlock(body, 0, body.Length, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
hmac.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
|
||||
return Convert.ToBase64String(hmac.Hash!);
|
||||
}
|
||||
|
||||
public static async Task<bool> ValidateRNSIG(HttpRequest request)
|
||||
{
|
||||
string method = request.Method.ToUpperInvariant();
|
||||
if (method != "POST" && method != "PUT")
|
||||
return true;
|
||||
|
||||
var rnsig = request.Headers["X-RNSIG"].FirstOrDefault()?.Trim().Trim('"');
|
||||
if (string.IsNullOrWhiteSpace(rnsig))
|
||||
return false;
|
||||
|
||||
var auth = request.Headers["Authorization"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
|
||||
return false;
|
||||
|
||||
var playerId = AuthStuff.GetPlayerId(request);
|
||||
if (playerId == null)
|
||||
return false;
|
||||
|
||||
var player = PlayerDB.Players.FindById(playerId.Value);
|
||||
bool isDeveloper = player.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false;
|
||||
if (isDeveloper)
|
||||
return true;
|
||||
|
||||
if (!_playerKeys.TryGetValue(playerId.Value, out var storedKeyB64) || string.IsNullOrWhiteSpace(storedKeyB64))
|
||||
return false;
|
||||
|
||||
byte[] keyBytes = Convert.FromBase64String(storedKeyB64);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
await request.Body.CopyToAsync(ms);
|
||||
byte[] body = ms.ToArray();
|
||||
request.Body.Position = 0;
|
||||
|
||||
string uri = request.Path.Value ?? "";
|
||||
string expected = ComputeSignature(keyBytes, uri, body);
|
||||
|
||||
return string.Equals(expected, rnsig, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user