mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 23:14:34 +01:00
implement client side session mangement
This commit is contained in:
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user