using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using MareSynchronos.API; using MareSynchronosServer.Authentication; using MareSynchronosServer.Data; using MareSynchronosServer.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; namespace MareSynchronosServer.Hubs { public class User : BaseHub { public User(MareDbContext dbContext) : base(dbContext) { } public async Task Register() { using var sha256 = SHA256.Create(); var computedHash = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(GenerateRandomString(64)))).Replace("-", ""); var user = new Models.User { SecretKey = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(computedHash))) .Replace("-", ""), }; var hasValidUid = false; while (!hasValidUid) { var uid = GenerateRandomString(10); if (DbContext.Users.Any(u => u.UID == uid)) continue; user.UID = uid; hasValidUid = true; } DbContext.Users.Add(user); await DbContext.SaveChangesAsync(); return computedHash; } private Whitelist OppositeEntry(string otherUID) => DbContext.Whitelists.SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == AuthenticatedUserId); [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public string GetUID() { return AuthenticatedUserId; } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task SendVisibilityList(List currentVisibilityList) { Stopwatch st = Stopwatch.StartNew(); var cid = DbContext.Users.Single(u => u.UID == AuthenticatedUserId).CharacterIdentification; var visibilities = DbContext.Visibilities.Where(v => v.CID == cid).ToList(); foreach (var visibility in currentVisibilityList.Where(visibility => visibilities.All(v => v.OtherCID != visibility))) { await DbContext.Visibilities.AddAsync(new Visibility { CID = cid, OtherCID = visibility }); } foreach (var visibility in visibilities.Where(v => currentVisibilityList.Contains(v.OtherCID))) { DbContext.Visibilities.Remove(visibility); } await DbContext.SaveChangesAsync(); st.Stop(); Debug.WriteLine("Visibility update took " + st.Elapsed); } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task GetCharacterData(Dictionary visibleCharacterWithJobs) { var uid = AuthenticatedUserId; Dictionary ret = new(); var whitelistEntriesHavingThisUser = DbContext.Whitelists .Include(w => w.User) .Include(w => w.OtherUser) .Where(w => w.OtherUser.UID == uid && !w.IsPaused && visibleCharacterWithJobs.Keys.Contains(w.User.CharacterIdentification)) .ToList(); foreach (var whiteListEntry in whitelistEntriesHavingThisUser) { bool isNotPaused = await DbContext.Whitelists.AnyAsync(w => !w.IsPaused && w.User.UID == uid && w.OtherUser.UID == whiteListEntry.User.UID); if (!isNotPaused) continue; var dictEntry = visibleCharacterWithJobs[whiteListEntry.User.CharacterIdentification]; var cachedChar = await DbContext.CharacterData.SingleOrDefaultAsync(c => c.UserId == whiteListEntry.User.UID && c.JobId == dictEntry); if (cachedChar != null) { await Clients.User(uid).SendAsync("ReceiveCharacterData", new CharacterCacheDto() { FileReplacements = cachedChar.EquipmentData, Hash = cachedChar.Hash, JobId = cachedChar.JobId, GlamourerData = cachedChar.GlamourerData }, whiteListEntry.User.CharacterIdentification); } } } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task PushCharacterData(CharacterCacheDto characterCache, List visibleCharacterIds) { var uid = AuthenticatedUserId; var whitelistEntriesHavingThisUser = DbContext.Whitelists .Include(w => w.User) .Include(w => w.OtherUser) .Where(w => w.OtherUser.UID == uid && !w.IsPaused && visibleCharacterIds.Contains(w.User.CharacterIdentification)).ToList(); var existingCharacterData = await DbContext.CharacterData.SingleOrDefaultAsync(s => s.UserId == uid && s.JobId == characterCache.JobId); if (existingCharacterData != null && existingCharacterData.Hash != characterCache.Hash) { existingCharacterData.EquipmentData = characterCache.FileReplacements; existingCharacterData.Hash = characterCache.Hash; existingCharacterData.GlamourerData = characterCache.GlamourerData; DbContext.CharacterData.Update(existingCharacterData); } else if (existingCharacterData == null) { CharacterData data = new CharacterData { UserId = AuthenticatedUserId, EquipmentData = characterCache.FileReplacements, Hash = characterCache.Hash, GlamourerData = characterCache.GlamourerData, JobId = characterCache.JobId }; await DbContext.CharacterData.AddAsync(data); await DbContext.SaveChangesAsync(); } if ((existingCharacterData != null && existingCharacterData.Hash != characterCache.Hash) || existingCharacterData == null) { foreach (var whitelistEntry in whitelistEntriesHavingThisUser) { var ownEntry = DbContext.Whitelists.SingleOrDefault(w => w.User.UID == uid && w.OtherUser.UID == whitelistEntry.User.UID); if (ownEntry == null || ownEntry.IsPaused) continue; await Clients.User(whitelistEntry.User.UID).SendAsync("ReceiveCharacterData", characterCache, whitelistEntry.OtherUser.CharacterIdentification); } } } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task> SendCharacterNameHash(string characterNameHash) { var ownUser = DbContext.Users.Single(u => u.UID == AuthenticatedUserId); ownUser.CharacterIdentification = characterNameHash; await DbContext.SaveChangesAsync(); var otherUsers = await DbContext.Whitelists .Include(u => u.User) .Include(u => u.OtherUser) .Where(w => w.User == ownUser) .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) .Select(e => e.OtherUser).ToListAsync(); var otherEntries = await DbContext.Whitelists.Include(u => u.User) .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == ownUser).ToListAsync(); await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync("AddOnlineWhitelistedPlayer", characterNameHash); return otherEntries.Select(e => e.User.CharacterIdentification).ToList(); } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task SendWhitelistAddition(string uid) { var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); if (otherUser == null) return; Whitelist wl = new Whitelist() { IsPaused = false, OtherUser = otherUser, User = user }; await DbContext.Whitelists.AddAsync(wl); await DbContext.SaveChangesAsync(); var otherEntry = OppositeEntry(uid); await Clients.User(user.UID) .SendAsync("UpdateWhitelist", new WhitelistDto() { OtherUID = otherUser.UID, IsPaused = false, IsPausedFromOthers = otherEntry?.IsPaused ?? false, IsSynced = otherEntry != null }, otherUser.CharacterIdentification); if (otherEntry != null) { if (string.IsNullOrEmpty(otherUser.CharacterIdentification)) { await Clients.User(user.UID) .SendAsync("AddOnlineWhitelistedPlayer", otherUser.CharacterIdentification); await Clients.User(otherUser.UID) .SendAsync("AddOnlineWhitelistedPlayer", user.CharacterIdentification); } await Clients.User(uid).SendAsync("UpdateWhitelist", new WhitelistDto() { OtherUID = user.UID, IsPaused = otherEntry.IsPaused, IsPausedFromOthers = false, IsSynced = true }, user.CharacterIdentification); } } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task SendWhitelistRemoval(string uid) { var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); if (otherUser == null) return; Whitelist wl = await DbContext.Whitelists.SingleOrDefaultAsync(w => w.User == user && w.OtherUser == otherUser); DbContext.Whitelists.Remove(wl); await DbContext.SaveChangesAsync(); var otherEntry = OppositeEntry(uid); await Clients.User(user.UID) .SendAsync("UpdateWhitelist", new WhitelistDto() { OtherUID = otherUser.UID, IsPaused = false, IsPausedFromOthers = otherEntry?.IsPaused ?? false, IsSynced = otherEntry != null }, otherUser.CharacterIdentification); if (otherEntry != null) { if (string.IsNullOrEmpty(otherUser.CharacterIdentification)) { await Clients.User(user.UID) .SendAsync("RemoveOnlineWhitelistedPlayer", otherUser.CharacterIdentification); await Clients.User(otherUser.UID) .SendAsync("RemoveOnlineWhitelistedPlayer", user.CharacterIdentification); } await Clients.User(uid).SendAsync("UpdateWhitelist", new WhitelistDto() { OtherUID = user.UID, IsPaused = otherEntry.IsPaused, IsPausedFromOthers = false, IsSynced = false }, user.CharacterIdentification); } } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task SendWhitelistPauseChange(string uid, bool isPaused) { var user = DbContext.Users.Single(u => u.UID == AuthenticatedUserId); var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); if (otherUser == null) return; Whitelist wl = await DbContext.Whitelists.SingleOrDefaultAsync(w => w.User == user && w.OtherUser == otherUser); wl.IsPaused = isPaused; DbContext.Update(wl); await DbContext.SaveChangesAsync(); var otherEntry = OppositeEntry(uid); await Clients.User(user.UID) .SendAsync("UpdateWhitelist", new WhitelistDto() { OtherUID = otherUser.UID, IsPaused = isPaused, IsPausedFromOthers = otherEntry?.IsPaused ?? false, IsSynced = otherEntry != null }, otherUser.CharacterIdentification); if (otherEntry != null) { await Clients.User(uid).SendAsync("UpdateWhitelist", new WhitelistDto() { OtherUID = user.UID, IsPaused = otherEntry.IsPaused, IsPausedFromOthers = isPaused, IsSynced = true }, user.CharacterIdentification); } } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AUTH_SCHEME)] public async Task> GetWhitelist() { string userid = AuthenticatedUserId; var user = GetAuthenticatedUser(); return DbContext.Whitelists .Include(u => u.OtherUser) .Include(u => u.User) .Where(w => w.User.UID == userid) .ToList() .Select(w => { var otherEntry = OppositeEntry(w.OtherUser.UID); return new WhitelistDto { IsPaused = w.IsPaused, OtherUID = w.OtherUser.UID, IsSynced = otherEntry != null, IsPausedFromOthers = otherEntry?.IsPaused ?? false, }; }).ToList(); } public override Task OnDisconnectedAsync(Exception exception) { var user = DbContext.Users.SingleOrDefault(u => u.UID == AuthenticatedUserId); if (user != null) { var otherUsers = DbContext.Whitelists .Include(u => u.User) .Include(u => u.OtherUser) .Where(w => w.User == user) .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) .Select(e => e.OtherUser).ToList(); var otherEntries = DbContext.Whitelists.Include(u => u.User) .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == user).ToList(); _ = Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync("RemoveOnlineWhitelistedPlayer", user.CharacterIdentification); var outdatedVisibilities = DbContext.Visibilities.Where(v => v.CID == user.CharacterIdentification); DbContext.RemoveRange(outdatedVisibilities); var outdatedCharacterData = DbContext.CharacterData.Where(v => v.UserId == user.UID); DbContext.RemoveRange(outdatedCharacterData); user.CharacterIdentification = null; DbContext.SaveChanges(); } return base.OnDisconnectedAsync(exception); } } }