diff --git a/EpinelPS/Database/JsonDb.cs b/EpinelPS/Database/JsonDb.cs index ed6e2f5..23e50f9 100644 --- a/EpinelPS/Database/JsonDb.cs +++ b/EpinelPS/Database/JsonDb.cs @@ -2,6 +2,8 @@ using EpinelPS.StaticInfo; using EpinelPS.Utils; using Newtonsoft.Json; +using Paseto.Builder; +using Paseto; namespace EpinelPS.Database { @@ -121,7 +123,7 @@ namespace EpinelPS.Database public NetJukeboxLocation Location; public NetJukeboxBgmType Type; public int TableId; - + } public class User { @@ -138,7 +140,7 @@ namespace EpinelPS.Database public bool ProfileIconIsPrism = false; public int ProfileFrame = 25; public bool IsAdmin = false; - public bool sickpulls = false; + public bool sickpulls = false; public bool IsBanned = false; public DateTime BanStart; public DateTime BanEnd; @@ -168,7 +170,7 @@ namespace EpinelPS.Database public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = []; public NetWallpaperPlaylist[] WallpaperPlaylistList = []; public NetWallpaperJukebox[] WallpaperJukeboxList = []; - + public Dictionary UserTeams = new Dictionary(); public Dictionary MainQuestData = new(); @@ -188,7 +190,7 @@ namespace EpinelPS.Database public Dictionary TowerProgress = new Dictionary(); - public JukeBoxSetting LobbyMusic = new() { Location = NetJukeboxLocation.NetJukeboxLocationLobby, TableId = 2, Type = NetJukeboxBgmType.NetJukeboxBgmTypeJukeboxTableId}; + public JukeBoxSetting LobbyMusic = new() { Location = NetJukeboxLocation.NetJukeboxLocationLobby, TableId = 2, Type = NetJukeboxBgmType.NetJukeboxBgmTypeJukeboxTableId }; public JukeBoxSetting CommanderMusic = new() { Location = NetJukeboxLocation.NetJukeboxLocationCommanderRoom, TableId = 5, Type = NetJukeboxBgmType.NetJukeboxBgmTypeJukeboxTableId }; // Event data @@ -343,8 +345,9 @@ namespace EpinelPS.Database public List LauncherAccessTokens = []; - public Dictionary GameClientTokens = []; public string ServerName = "Private Server"; + public byte[] LauncherTokenKey = []; + public byte[] EncryptionTokenKey = []; } internal class JsonDb { @@ -423,6 +426,24 @@ namespace EpinelPS.Database } Console.WriteLine("Database update completed"); } + + if (Instance.LauncherTokenKey.Length == 0) + { + Console.WriteLine("Launcher token key is null, generating new key"); + + var pasetoKey = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local) + .GenerateSymmetricKey(); + Instance.LauncherTokenKey = pasetoKey.Key.ToArray(); + } + if (Instance.EncryptionTokenKey.Length == 0) + { + Console.WriteLine("EncryptionTokenKey is null, generating new key"); + + var pasetoKey = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local) + .GenerateSymmetricKey(); + Instance.EncryptionTokenKey = pasetoKey.Key.ToArray(); + } + Save(); ValidateDb(); @@ -437,9 +458,9 @@ namespace EpinelPS.Database private static void ValidateDb() { // check if character level is valid - foreach (var item in Instance.Users) + foreach (var user in Instance.Users) { - foreach (var c in item.Characters) + foreach (var c in user.Characters) { if (c.Level > 1000) { @@ -447,6 +468,26 @@ namespace EpinelPS.Database c.Level = 1000; } } + + // Check if RepresentationTeamData exists and has slots + if (user.RepresentationTeamData != null && user.RepresentationTeamData.Slots != null) + { + // Iterate through RepresentationTeamData slots + foreach (var slot in user.RepresentationTeamData.Slots) + { + // Find the character in user's character list that matches the slot's Tid + var correspondingCharacter = user.Characters.FirstOrDefault(c => c.Tid == slot.Tid); + + if (correspondingCharacter != null) + { + // Update the CSN value if it differs + if (slot.Csn != correspondingCharacter.Csn) + { + slot.Csn = correspondingCharacter.Csn; + } + } + } + } } } @@ -461,44 +502,44 @@ namespace EpinelPS.Database File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json", JsonConvert.SerializeObject(Instance, Formatting.Indented)); } } - public static int CurrentJukeboxBgm(int position) - { - var activeJukeboxBgm = new List(); - //important first position holds lobby bgm id and second commanders room bgm id - foreach (var user in Instance.Users) - { - if (user.JukeboxBgm == null || user.JukeboxBgm.Count == 0) - { - // this if statemet only exists becaus some weird black magic copies default value over and over - //in the file when its set in public List JukeboxBgm = new List(); - //delete when or if it gets fixed - - user.JukeboxBgm = new List { 2,5 }; - } + public static int CurrentJukeboxBgm(int position) + { + var activeJukeboxBgm = new List(); + //important first position holds lobby bgm id and second commanders room bgm id + foreach (var user in Instance.Users) + { + if (user.JukeboxBgm == null || user.JukeboxBgm.Count == 0) + { + // this if statemet only exists becaus some weird black magic copies default value over and over + //in the file when its set in public List JukeboxBgm = new List(); + //delete when or if it gets fixed - activeJukeboxBgm.AddRange(user.JukeboxBgm); - } + user.JukeboxBgm = new List { 2, 5 }; + } - if (activeJukeboxBgm.Count == 0) - { - return 8995001; - } + activeJukeboxBgm.AddRange(user.JukeboxBgm); + } - position = (position == 2 && activeJukeboxBgm.Count > 1) ? 2 : 1; - return activeJukeboxBgm[position - 1]; - } + if (activeJukeboxBgm.Count == 0) + { + return 8995001; + } - public static bool IsSickPulls(User selectedUser) - { - if (selectedUser != null) - { - return selectedUser.sickpulls; - } - else - { - throw new Exception($"User with ID {selectedUser.ID} not found"); - } - } + position = (position == 2 && activeJukeboxBgm.Count > 1) ? 2 : 1; + return activeJukeboxBgm[position - 1]; + } + + public static bool IsSickPulls(User selectedUser) + { + if (selectedUser != null) + { + return selectedUser.sickpulls; + } + else + { + throw new Exception($"User with ID {selectedUser.ID} not found"); + } + } } } diff --git a/EpinelPS/LobbyServer/LobbyHandler.cs b/EpinelPS/LobbyServer/LobbyHandler.cs index 27c4875..17a6a15 100644 --- a/EpinelPS/LobbyServer/LobbyHandler.cs +++ b/EpinelPS/LobbyServer/LobbyHandler.cs @@ -2,6 +2,9 @@ using EpinelPS.Database; using EpinelPS.Utils; using Google.Protobuf; +using Paseto.Builder; +using Paseto; +using Newtonsoft.Json; namespace EpinelPS.LobbyServer { @@ -64,7 +67,7 @@ namespace EpinelPS.LobbyServer /// /// /// - public static GameClientInfo GenGameClientTok(ByteString publicKey, string authToken) + public static GameClientInfo GenGameClientTok(ByteString publicKey, ulong userid) { var token = Rng.RandomString(381); @@ -75,25 +78,23 @@ namespace EpinelPS.LobbyServer info.Keys = box; info.ClientAuthToken = token; - // look up user id - foreach (var user in JsonDb.Instance.LauncherAccessTokens) - { - if (user.Token == authToken) - { - info.UserId = user.UserID; - } - } - if (info.UserId == 0) + if (userid == 0) throw new Exception("expected user account"); - JsonDb.Instance.GameClientTokens.Add(token, info); - JsonDb.Save(); + info.UserId = userid; + return info; } public static GameClientInfo? GetInfo(string token) { - return JsonDb.Instance.GameClientTokens[token]; + var encryptionToken = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local) + .WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey) + .Decode(token, new PasetoTokenValidationParameters() { ValidateLifetime = true }); + + var p = ((System.Text.Json.JsonElement)encryptionToken.Paseto.Payload["data"]).GetString(); + + return JsonConvert.DeserializeObject(p); } public static void Init() diff --git a/EpinelPS/LobbyServer/LobbyMsgHandler.cs b/EpinelPS/LobbyServer/LobbyMsgHandler.cs index 74d1c9d..cbd9d8c 100644 --- a/EpinelPS/LobbyServer/LobbyMsgHandler.cs +++ b/EpinelPS/LobbyServer/LobbyMsgHandler.cs @@ -1,6 +1,11 @@ using Google.Protobuf; using EpinelPS.Database; using EpinelPS.Utils; +using Newtonsoft.Json; +using Paseto.Builder; +using Paseto; +using System.Security.Cryptography; +using Newtonsoft.Json.Linq; namespace EpinelPS.LobbyServer { @@ -41,13 +46,13 @@ namespace EpinelPS.LobbyServer public async Task HandleAsync(string authToken) { this.UsedAuthToken = authToken; - foreach (var item in JsonDb.Instance.GameClientTokens) - { - if (item.Key == authToken) - { - UserId = item.Value.UserId; - } - } + + var encryptionToken = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local) + .WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey) + .Decode(authToken, new PasetoTokenValidationParameters() { ValidateLifetime = true}); + + UserId = ((System.Text.Json.JsonElement)encryptionToken.Paseto.Payload["userid"]).GetUInt64(); + if (UserId == 0) throw new Exception("403"); await HandleAsync(); } diff --git a/EpinelPS/LobbyServer/Msgs/Auth/AuthLogout.cs b/EpinelPS/LobbyServer/Msgs/Auth/AuthLogout.cs index 7bf869a..3c49d43 100644 --- a/EpinelPS/LobbyServer/Msgs/Auth/AuthLogout.cs +++ b/EpinelPS/LobbyServer/Msgs/Auth/AuthLogout.cs @@ -10,7 +10,7 @@ namespace EpinelPS.LobbyServer.Msgs.Auth { var req = await ReadData(); - JsonDb.Instance.GameClientTokens.Remove(UsedAuthToken); + // TODO remove UsedAuthToken await WriteDataAsync(new ResLogout()); } diff --git a/EpinelPS/LobbyServer/Msgs/Auth/DoEnterServer.cs b/EpinelPS/LobbyServer/Msgs/Auth/DoEnterServer.cs index b08bf19..353fc2c 100644 --- a/EpinelPS/LobbyServer/Msgs/Auth/DoEnterServer.cs +++ b/EpinelPS/LobbyServer/Msgs/Auth/DoEnterServer.cs @@ -2,6 +2,10 @@ using EpinelPS.Utils; using Google.Protobuf; using Google.Protobuf.WellKnownTypes; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Paseto.Builder; +using Paseto; +using Newtonsoft.Json; namespace EpinelPS.LobbyServer.Msgs.Auth { @@ -22,17 +26,33 @@ namespace EpinelPS.LobbyServer.Msgs.Auth } } if (UserId == 0) throw new BadHttpRequestException("unknown auth token", 403); - var user = GetUser(); + var rsp = LobbyHandler.GenGameClientTok(req.ClientPublicKey, UserId); + + var token = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local) + .WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey) + .AddClaim("userid", UserId) + .IssuedAt(DateTime.UtcNow) + .Expiration(DateTime.UtcNow.AddDays(2)) + .Encode(); + + var encryptionToken = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local) + .WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey) + .AddClaim("data", JsonConvert.SerializeObject(rsp)) + .IssuedAt(DateTime.UtcNow) + .Expiration(DateTime.UtcNow.AddDays(2)) + .Encode(); + + var response = new ResEnterServer(); - var rsp = LobbyHandler.GenGameClientTok(req.ClientPublicKey, req.AuthToken); - response.GameClientToken = rsp.ClientAuthToken; + + response.GameClientToken = token; response.FeatureDataInfo = new NetFeatureDataInfo() { UseFeatureData = true }; - response.Identifier = new NetLegacyUserIdentifier() { Server = 21769, Usn = (long)user.ID }; + response.Identifier = new NetLegacyUserIdentifier() { Server = 1000, Usn = (long)user.ID }; response.ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400)); - response.EncryptionToken = ByteString.CopyFromUtf8(rsp.ClientAuthToken); + response.EncryptionToken = ByteString.CopyFromUtf8(encryptionToken); await WriteDataAsync(response); } } diff --git a/EpinelPS/Program.cs b/EpinelPS/Program.cs index 1eca6c7..0b0cc88 100644 --- a/EpinelPS/Program.cs +++ b/EpinelPS/Program.cs @@ -29,32 +29,6 @@ namespace EpinelPS Console.WriteLine("Initialize handlers"); LobbyHandler.Init(); - foreach (var user in JsonDb.Instance.Users) - { - // Check if RepresentationTeamData exists and has slots - if (user.RepresentationTeamData != null && user.RepresentationTeamData.Slots != null) - { - // Iterate through RepresentationTeamData slots - foreach (var slot in user.RepresentationTeamData.Slots) - { - // Find the character in user's character list that matches the slot's Tid - var correspondingCharacter = user.Characters.FirstOrDefault(c => c.Tid == slot.Tid); - - if (correspondingCharacter != null) - { - // Update the CSN value if it differs - if (slot.Csn != correspondingCharacter.Csn) - { - slot.Csn = correspondingCharacter.Csn; - } - } - } - } - } - - // Save the updated data - JsonDb.Save(); - Console.WriteLine("Starting ASP.NET core on ports 80/443"); new Thread(() => {