implement client side session mangement

This commit is contained in:
Mikhail
2024-09-21 14:45:08 -04:00
parent 3f32e569d9
commit 7ac711b027
6 changed files with 134 additions and 93 deletions

View File

@@ -2,6 +2,8 @@
using EpinelPS.StaticInfo; using EpinelPS.StaticInfo;
using EpinelPS.Utils; using EpinelPS.Utils;
using Newtonsoft.Json; using Newtonsoft.Json;
using Paseto.Builder;
using Paseto;
namespace EpinelPS.Database namespace EpinelPS.Database
{ {
@@ -121,7 +123,7 @@ namespace EpinelPS.Database
public NetJukeboxLocation Location; public NetJukeboxLocation Location;
public NetJukeboxBgmType Type; public NetJukeboxBgmType Type;
public int TableId; public int TableId;
} }
public class User public class User
{ {
@@ -138,7 +140,7 @@ namespace EpinelPS.Database
public bool ProfileIconIsPrism = false; public bool ProfileIconIsPrism = false;
public int ProfileFrame = 25; public int ProfileFrame = 25;
public bool IsAdmin = false; public bool IsAdmin = false;
public bool sickpulls = false; public bool sickpulls = false;
public bool IsBanned = false; public bool IsBanned = false;
public DateTime BanStart; public DateTime BanStart;
public DateTime BanEnd; public DateTime BanEnd;
@@ -168,7 +170,7 @@ namespace EpinelPS.Database
public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = []; public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = [];
public NetWallpaperPlaylist[] WallpaperPlaylistList = []; public NetWallpaperPlaylist[] WallpaperPlaylistList = [];
public NetWallpaperJukebox[] WallpaperJukeboxList = []; public NetWallpaperJukebox[] WallpaperJukeboxList = [];
public Dictionary<int, NetUserTeamData> UserTeams = new Dictionary<int, NetUserTeamData>(); public Dictionary<int, NetUserTeamData> UserTeams = new Dictionary<int, NetUserTeamData>();
public Dictionary<int, bool> MainQuestData = new(); public Dictionary<int, bool> MainQuestData = new();
@@ -188,7 +190,7 @@ namespace EpinelPS.Database
public Dictionary<int, int> TowerProgress = new Dictionary<int, int>(); public Dictionary<int, int> TowerProgress = new Dictionary<int, int>();
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 }; public JukeBoxSetting CommanderMusic = new() { Location = NetJukeboxLocation.NetJukeboxLocationCommanderRoom, TableId = 5, Type = NetJukeboxBgmType.NetJukeboxBgmTypeJukeboxTableId };
// Event data // Event data
@@ -343,8 +345,9 @@ namespace EpinelPS.Database
public List<AccessToken> LauncherAccessTokens = []; public List<AccessToken> LauncherAccessTokens = [];
public Dictionary<string, GameClientInfo> GameClientTokens = [];
public string ServerName = "<color=\"green\">Private Server</color>"; public string ServerName = "<color=\"green\">Private Server</color>";
public byte[] LauncherTokenKey = [];
public byte[] EncryptionTokenKey = [];
} }
internal class JsonDb internal class JsonDb
{ {
@@ -423,6 +426,24 @@ namespace EpinelPS.Database
} }
Console.WriteLine("Database update completed"); 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(); Save();
ValidateDb(); ValidateDb();
@@ -437,9 +458,9 @@ namespace EpinelPS.Database
private static void ValidateDb() private static void ValidateDb()
{ {
// check if character level is valid // 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) if (c.Level > 1000)
{ {
@@ -447,6 +468,26 @@ namespace EpinelPS.Database
c.Level = 1000; 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)); File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json", JsonConvert.SerializeObject(Instance, Formatting.Indented));
} }
} }
public static int CurrentJukeboxBgm(int position) public static int CurrentJukeboxBgm(int position)
{ {
var activeJukeboxBgm = new List<int>(); var activeJukeboxBgm = new List<int>();
//important first position holds lobby bgm id and second commanders room bgm id //important first position holds lobby bgm id and second commanders room bgm id
foreach (var user in Instance.Users) foreach (var user in Instance.Users)
{ {
if (user.JukeboxBgm == null || user.JukeboxBgm.Count == 0) if (user.JukeboxBgm == null || user.JukeboxBgm.Count == 0)
{ {
// this if statemet only exists becaus some weird black magic copies default value over and over // 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<int> JukeboxBgm = new List<int>(); //in the file when its set in public List<int> JukeboxBgm = new List<int>();
//delete when or if it gets fixed //delete when or if it gets fixed
user.JukeboxBgm = new List<int> { 2,5 };
}
activeJukeboxBgm.AddRange(user.JukeboxBgm); user.JukeboxBgm = new List<int> { 2, 5 };
} }
if (activeJukeboxBgm.Count == 0) activeJukeboxBgm.AddRange(user.JukeboxBgm);
{ }
return 8995001;
}
position = (position == 2 && activeJukeboxBgm.Count > 1) ? 2 : 1; if (activeJukeboxBgm.Count == 0)
return activeJukeboxBgm[position - 1]; {
} return 8995001;
}
public static bool IsSickPulls(User selectedUser) position = (position == 2 && activeJukeboxBgm.Count > 1) ? 2 : 1;
{ return activeJukeboxBgm[position - 1];
if (selectedUser != null) }
{
return selectedUser.sickpulls; public static bool IsSickPulls(User selectedUser)
} {
else if (selectedUser != null)
{ {
throw new Exception($"User with ID {selectedUser.ID} not found"); return selectedUser.sickpulls;
} }
} else
{
throw new Exception($"User with ID {selectedUser.ID} not found");
}
}
} }
} }

View File

@@ -2,6 +2,9 @@
using EpinelPS.Database; using EpinelPS.Database;
using EpinelPS.Utils; using EpinelPS.Utils;
using Google.Protobuf; using Google.Protobuf;
using Paseto.Builder;
using Paseto;
using Newtonsoft.Json;
namespace EpinelPS.LobbyServer namespace EpinelPS.LobbyServer
{ {
@@ -64,7 +67,7 @@ namespace EpinelPS.LobbyServer
/// </summary> /// </summary>
/// <param name="publicKey"></param> /// <param name="publicKey"></param>
/// <returns></returns> /// <returns></returns>
public static GameClientInfo GenGameClientTok(ByteString publicKey, string authToken) public static GameClientInfo GenGameClientTok(ByteString publicKey, ulong userid)
{ {
var token = Rng.RandomString(381); var token = Rng.RandomString(381);
@@ -75,25 +78,23 @@ namespace EpinelPS.LobbyServer
info.Keys = box; info.Keys = box;
info.ClientAuthToken = token; info.ClientAuthToken = token;
// look up user id if (userid == 0)
foreach (var user in JsonDb.Instance.LauncherAccessTokens)
{
if (user.Token == authToken)
{
info.UserId = user.UserID;
}
}
if (info.UserId == 0)
throw new Exception("expected user account"); throw new Exception("expected user account");
JsonDb.Instance.GameClientTokens.Add(token, info); info.UserId = userid;
JsonDb.Save();
return info; return info;
} }
public static GameClientInfo? GetInfo(string token) 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<GameClientInfo>(p);
} }
public static void Init() public static void Init()

View File

@@ -1,6 +1,11 @@
using Google.Protobuf; using Google.Protobuf;
using EpinelPS.Database; using EpinelPS.Database;
using EpinelPS.Utils; using EpinelPS.Utils;
using Newtonsoft.Json;
using Paseto.Builder;
using Paseto;
using System.Security.Cryptography;
using Newtonsoft.Json.Linq;
namespace EpinelPS.LobbyServer namespace EpinelPS.LobbyServer
{ {
@@ -41,13 +46,13 @@ namespace EpinelPS.LobbyServer
public async Task HandleAsync(string authToken) public async Task HandleAsync(string authToken)
{ {
this.UsedAuthToken = authToken; this.UsedAuthToken = authToken;
foreach (var item in JsonDb.Instance.GameClientTokens)
{ var encryptionToken = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
if (item.Key == authToken) .WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey)
{ .Decode(authToken, new PasetoTokenValidationParameters() { ValidateLifetime = true});
UserId = item.Value.UserId;
} UserId = ((System.Text.Json.JsonElement)encryptionToken.Paseto.Payload["userid"]).GetUInt64();
}
if (UserId == 0) throw new Exception("403"); if (UserId == 0) throw new Exception("403");
await HandleAsync(); await HandleAsync();
} }

View File

@@ -10,7 +10,7 @@ namespace EpinelPS.LobbyServer.Msgs.Auth
{ {
var req = await ReadData<ReqLogout>(); var req = await ReadData<ReqLogout>();
JsonDb.Instance.GameClientTokens.Remove(UsedAuthToken); // TODO remove UsedAuthToken
await WriteDataAsync(new ResLogout()); await WriteDataAsync(new ResLogout());
} }

View File

@@ -2,6 +2,10 @@
using EpinelPS.Utils; using EpinelPS.Utils;
using Google.Protobuf; using Google.Protobuf;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Paseto.Builder;
using Paseto;
using Newtonsoft.Json;
namespace EpinelPS.LobbyServer.Msgs.Auth namespace EpinelPS.LobbyServer.Msgs.Auth
{ {
@@ -22,17 +26,33 @@ namespace EpinelPS.LobbyServer.Msgs.Auth
} }
} }
if (UserId == 0) throw new BadHttpRequestException("unknown auth token", 403); if (UserId == 0) throw new BadHttpRequestException("unknown auth token", 403);
var user = GetUser(); 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 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.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.ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400));
response.EncryptionToken = ByteString.CopyFromUtf8(rsp.ClientAuthToken); response.EncryptionToken = ByteString.CopyFromUtf8(encryptionToken);
await WriteDataAsync(response); await WriteDataAsync(response);
} }
} }

View File

@@ -29,32 +29,6 @@ namespace EpinelPS
Console.WriteLine("Initialize handlers"); Console.WriteLine("Initialize handlers");
LobbyHandler.Init(); 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"); Console.WriteLine("Starting ASP.NET core on ports 80/443");
new Thread(() => new Thread(() =>
{ {