diff --git a/MareSynchronosServer/MareSynchronos.API/API.cs b/MareSynchronosServer/MareSynchronos.API/API.cs new file mode 100644 index 0000000..990f9af --- /dev/null +++ b/MareSynchronosServer/MareSynchronos.API/API.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MareSynchronos.API +{ + public class API + { + public const int Version = 2; + } + + public class FilesHubAPI + { + public const string Path = "/files"; + + public const string SendAbortUpload = "AbortUpload"; + public const string InvokeSendFiles = "SendFiles"; + public const string InvokeIsUploadFinished = "IsUploadFinished"; + public const string SendUploadFileStreamAsync = "UploadFileStreamAsync"; + public const string InvokeGetFileSize = "GetFileSize"; + public const string StreamDownloadFileAsync = "StreamDownloadFileAsync"; + public const string SendDeleteAllFiles = "DeleteAllFiles"; + } + + public class ConnectionHubAPI + { + public const string Path = "/heartbeat"; + public const string InvokeHeartbeat = "Heartbeat"; + } + + public class AdminHubAPI + { + public const string Path = "/admin"; + + public const string InvokeGetOnlineUsers = "GetOnlineUsers"; + public const string InvokeGetBannedUsers = "GetBannedUsers"; + public const string SendUpdateOrAddBannedUser = "UpdateOrAddBannedUser"; + public const string SendDeleteBannedUser = "DeleteBannedUser"; + public const string InvokeGetForbiddenFiles = "GetForbiddenFiles"; + public const string SendUpdateOrAddForbiddenFile = "UpdateOrAddForbiddenFile"; + public const string SendDeleteForbiddenFile = "DeleteForbiddenFile"; + public const string SendChangeModeratorStatus = "ChangeModeratorStatus"; + + public const string OnForcedReconnect = "ForcedReconnect"; + public const string OnUpdateOrAddBannedUser = "UpdateOrAddBannedUser"; + public const string OnDeleteBannedUser = "DeleteBannedUser"; + public const string OnUpdateOrAddForbiddenFile = "UpdateOrAddForbiddenFile"; + public const string OnDeleteForbiddenFile = "DeleteForbiddenFile"; + } + + public class UserHubAPI + { + public const string Path = "/user"; + + public const string InvokeGetOnlineUsers = "GetOnlineUsers"; + public const string InvokeRegister = "Register"; + public const string InvokePushCharacterDataToVisibleClients = "PushCharacterDataToVisibleClients"; + public const string InvokeGetOnlineCharacters = "GetOnlineCharacters"; + public const string SendPairedClientAddition = "SendPairedClientAddition"; + public const string SendPairedClientRemoval = "SendPairedClientRemoval"; + public const string SendPairedClientPauseChange = "SendPairedClientPauseChange"; + public const string InvokeGetPairedClients = "GetPairedClients"; + public const string SendDeleteAccount = "DeleteAccount"; + + public const string OnUsersOnline = "UsersOnline"; + public const string OnUpdateClientPairs = "UpdateClientPairs"; + public const string OnReceiveCharacterData = "ReceiveCharacterData"; + public const string OnRemoveOnlinePairedPlayer = "RemoveOnlinePairedPlayer"; + public const string OnAddOnlinePairedPlayer = "AddOnlinePairedPlayer"; + } +} diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/AdminHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/AdminHub.cs index 236c3bf..f0bafae 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/AdminHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/AdminHub.cs @@ -18,13 +18,91 @@ namespace MareSynchronosServer.Hubs { } - private List OnlineAdmins => DbContext.Users.Where(u => !string.IsNullOrEmpty(u.CharacterIdentification) && (u.IsModerator || u.IsAdmin)) - .Select(u => u.UID).ToList(); - - private bool IsModerator => DbContext.Users.Single(b => b.UID == AuthenticatedUserId).IsModerator || IsAdmin; private bool IsAdmin => DbContext.Users.Single(b => b.UID == AuthenticatedUserId).IsAdmin; + private bool IsModerator => DbContext.Users.Single(b => b.UID == AuthenticatedUserId).IsModerator || IsAdmin; + + private List OnlineAdmins => DbContext.Users.Where(u => !string.IsNullOrEmpty(u.CharacterIdentification) && (u.IsModerator || u.IsAdmin)) + .Select(u => u.UID).ToList(); [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.SendChangeModeratorStatus)] + public async Task ChangeModeratorStatus(string uid, bool isModerator) + { + if (!IsAdmin) return; + var user = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); + + if (user == null) return; + + user.IsModerator = isModerator; + DbContext.Update(user); + await DbContext.SaveChangesAsync(); + await Clients.Users(user.UID).SendAsync(AdminHubAPI.OnForcedReconnect); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.SendDeleteBannedUser)] + public async Task DeleteBannedUser(BannedUserDto dto) + { + if (!IsModerator || string.IsNullOrEmpty(dto.CharacterHash)) return; + + var existingUser = + await DbContext.BannedUsers.SingleOrDefaultAsync(b => b.CharacterIdentification == dto.CharacterHash); + if (existingUser == null) + { + return; + } + + DbContext.Remove(existingUser); + await DbContext.SaveChangesAsync(); + await Clients.Users(OnlineAdmins).SendAsync(AdminHubAPI.OnDeleteBannedUser, dto); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.SendDeleteForbiddenFile)] + public async Task DeleteForbiddenFile(ForbiddenFileDto dto) + { + if (!IsAdmin || string.IsNullOrEmpty(dto.Hash)) return; + + var existingFile = + await DbContext.ForbiddenUploadEntries.SingleOrDefaultAsync(b => b.Hash == dto.Hash); + if (existingFile == null) + { + return; + } + + DbContext.Remove(existingFile); + await DbContext.SaveChangesAsync(); + await Clients.Users(OnlineAdmins).SendAsync(AdminHubAPI.OnDeleteForbiddenFile, dto); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.InvokeGetBannedUsers)] + public async Task> GetBannedUsers() + { + if (!IsModerator) return null; + + return await DbContext.BannedUsers.Select(b => new BannedUserDto() + { + CharacterHash = b.CharacterIdentification, + Reason = b.Reason + }).ToListAsync(); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.InvokeGetForbiddenFiles)] + public async Task> GetForbiddenFiles() + { + if (!IsModerator) return null; + + return await DbContext.ForbiddenUploadEntries.Select(b => new ForbiddenFileDto() + { + Hash = b.Hash, + ForbiddenBy = b.ForbiddenBy + }).ToListAsync(); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.InvokeGetOnlineUsers)] public async Task> GetOnlineUsers() { if (!IsModerator) return null; @@ -39,18 +117,7 @@ namespace MareSynchronosServer.Hubs } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task> GetBannedUsers() - { - if (!IsModerator) return null; - - return await DbContext.BannedUsers.Select(b => new BannedUserDto() - { - CharacterHash = b.CharacterIdentification, - Reason = b.Reason - }).ToListAsync(); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.SendUpdateOrAddBannedUser)] public async Task UpdateOrAddBannedUser(BannedUserDto dto) { if (!IsModerator || string.IsNullOrEmpty(dto.CharacterHash)) return; @@ -72,45 +139,17 @@ namespace MareSynchronosServer.Hubs } await DbContext.SaveChangesAsync(); - await Clients.Users(OnlineAdmins).SendAsync("UpdateOrAddBannedUser", dto); + await Clients.Users(OnlineAdmins).SendAsync(AdminHubAPI.OnUpdateOrAddBannedUser, dto); var bannedUser = await DbContext.Users.SingleOrDefaultAsync(u => u.CharacterIdentification == dto.CharacterHash); if (bannedUser != null) { - await Clients.User(bannedUser.UID).SendAsync("ForcedReconnect"); + await Clients.User(bannedUser.UID).SendAsync(AdminHubAPI.OnForcedReconnect); } } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task DeleteBannedUser(BannedUserDto dto) - { - if (!IsModerator || string.IsNullOrEmpty(dto.CharacterHash)) return; - - var existingUser = - await DbContext.BannedUsers.SingleOrDefaultAsync(b => b.CharacterIdentification == dto.CharacterHash); - if (existingUser == null) - { - return; - } - - DbContext.Remove(existingUser); - await DbContext.SaveChangesAsync(); - await Clients.Users(OnlineAdmins).SendAsync("DeleteBannedUser", dto); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task> GetForbiddenFiles() - { - if (!IsModerator) return null; - - return await DbContext.ForbiddenUploadEntries.Select(b => new ForbiddenFileDto() - { - Hash = b.Hash, - ForbiddenBy = b.ForbiddenBy - }).ToListAsync(); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(AdminHubAPI.SendUpdateOrAddForbiddenFile)] public async Task UpdateOrAddForbiddenFile(ForbiddenFileDto dto) { if (!IsAdmin || string.IsNullOrEmpty(dto.Hash)) return; @@ -133,39 +172,7 @@ namespace MareSynchronosServer.Hubs await DbContext.SaveChangesAsync(); - await Clients.Users(OnlineAdmins).SendAsync("UpdateOrAddForbiddenFile", dto); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task DeleteForbiddenFile(ForbiddenFileDto dto) - { - if (!IsAdmin || string.IsNullOrEmpty(dto.Hash)) return; - - var existingFile = - await DbContext.ForbiddenUploadEntries.SingleOrDefaultAsync(b => b.Hash == dto.Hash); - if (existingFile == null) - { - return; - } - - DbContext.Remove(existingFile); - await DbContext.SaveChangesAsync(); - await Clients.Users(OnlineAdmins).SendAsync("DeleteForbiddenFile", dto); - } - - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task ChangeModeratorStatus(string uid, bool isModerator) - { - if (!IsAdmin) return; - var user = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); - - if (user == null) return; - - user.IsModerator = isModerator; - DbContext.Update(user); - await DbContext.SaveChangesAsync(); - await Clients.Users(user.UID).SendAsync("ForcedReconnect"); + await Clients.Users(OnlineAdmins).SendAsync(AdminHubAPI.OnUpdateOrAddForbiddenFile, dto); } } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs index b0c22dc..5d175ce 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/ConnectionHub.cs @@ -4,6 +4,7 @@ using System.Security.Claims; using System.Threading.Tasks; using MareSynchronos.API; using MareSynchronosServer.Data; +using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -11,11 +12,11 @@ namespace MareSynchronosServer.Hubs { public class ConnectionHub : BaseHub { - private const int ServerVersion = 1; public ConnectionHub(MareDbContext mareDbContext, ILogger logger) : base(mareDbContext, logger) { } + [HubMethodName(ConnectionHubAPI.InvokeHeartbeat)] public async Task Heartbeat() { var userId = Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; @@ -27,7 +28,7 @@ namespace MareSynchronosServer.Hubs var user = (await DbContext.Users.SingleAsync(u => u.UID == userId)); return new ConnectionDto { - ServerVersion = ServerVersion, + ServerVersion = API.Version, UID = userId, IsModerator = user.IsModerator, IsAdmin = user.IsAdmin diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs index 1a7079d..f80b0d5 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/FilesHub.cs @@ -12,6 +12,7 @@ using MareSynchronosServer.Authentication; using MareSynchronosServer.Data; using MareSynchronosServer.Models; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -30,6 +31,7 @@ namespace MareSynchronosServer.Hubs private string BasePath => _configuration["CacheDirectory"]; [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.SendAbortUpload)] public async Task AbortUpload() { Logger.LogInformation("User " + AuthenticatedUserId + " aborted upload"); @@ -40,6 +42,102 @@ namespace MareSynchronosServer.Hubs } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.SendDeleteAllFiles)] + public async Task DeleteAllFiles() + { + Logger.LogInformation("User " + AuthenticatedUserId + " deleted all their files"); + + DbContext.CharacterData.RemoveRange(DbContext.CharacterData.Where(c => c.UserId == AuthenticatedUserId)); + await DbContext.SaveChangesAsync(); + var ownFiles = await DbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == AuthenticatedUserId).ToListAsync(); + foreach (var file in ownFiles) + { + File.Delete(Path.Combine(BasePath, file.Hash)); + } + DbContext.Files.RemoveRange(ownFiles); + await DbContext.SaveChangesAsync(); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.StreamDownloadFileAsync)] + public async IAsyncEnumerable DownloadFileAsync(string hash, [EnumeratorCancellation] CancellationToken ct) + { + Logger.LogInformation("User " + AuthenticatedUserId + " downloading file: " + hash); + + var file = DbContext.Files.SingleOrDefault(f => f.Hash == hash); + if (file == null) yield break; + file.LastAccessTime = DateTime.Now; + DbContext.Update(file); + await DbContext.SaveChangesAsync(ct); + var chunkSize = 1024 * 512; // 512kb + int readByteCount; + var buffer = new byte[chunkSize]; + + await using var fs = File.Open(Path.Combine(BasePath, hash), FileMode.Open, FileAccess.Read); + while ((readByteCount = await fs.ReadAsync(buffer, 0, chunkSize, ct)) > 0) + { + await Task.Delay(10, ct); + yield return readByteCount == chunkSize ? buffer.ToArray() : buffer.Take(readByteCount).ToArray(); + } + + Logger.LogInformation("User " + AuthenticatedUserId + " finished downloading file: " + hash); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.InvokeGetFileSize)] + public async Task GetFileSize(string hash) + { + var file = await DbContext.Files.SingleOrDefaultAsync(f => f.Hash == hash); + var forbidden = DbContext.ForbiddenUploadEntries.SingleOrDefault(f => f.Hash == hash); + var fileInfo = new FileInfo(Path.Combine(BasePath, hash)); + long fileSize = 0; + try + { + fileSize = fileInfo.Length; + } + catch + { + // file doesn't exist anymore + } + + var response = new DownloadFileDto + { + FileExists = fileInfo.Exists, + ForbiddenBy = forbidden?.ForbiddenBy ?? string.Empty, + IsForbidden = forbidden != null, + Hash = hash, + Size = fileSize + }; + + if (!fileInfo.Exists && file != null) + { + DbContext.Files.Remove(file); + await DbContext.SaveChangesAsync(); + } + + return response; + + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.InvokeIsUploadFinished)] + public async Task IsUploadFinished() + { + var userUid = AuthenticatedUserId; + return await DbContext.Files.AnyAsync(f => f.Uploader.UID == userUid && !f.Uploaded); + } + + public override Task OnDisconnectedAsync(Exception exception) + { + var userId = AuthenticatedUserId; + var notUploadedFiles = DbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == userId).ToList(); + DbContext.RemoveRange(notUploadedFiles); + DbContext.SaveChanges(); + return base.OnDisconnectedAsync(exception); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.InvokeSendFiles)] public async Task> SendFiles(List fileListHashes) { fileListHashes = fileListHashes.Distinct().ToList(); @@ -76,13 +174,7 @@ namespace MareSynchronosServer.Hubs } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task IsUploadFinished() - { - var userUid = AuthenticatedUserId; - return await DbContext.Files.AnyAsync(f => f.Uploader.UID == userUid && !f.Uploaded); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(FilesHubAPI.SendUploadFileStreamAsync)] public async Task UploadFileStreamAsync(string hash, IAsyncEnumerable fileContent) { Logger.LogInformation("User " + AuthenticatedUserId + " uploading file: " + hash); @@ -101,9 +193,9 @@ namespace MareSynchronosServer.Hubs } catch { - DbContext.Files.Remove(relatedFile); try { + DbContext.Files.Remove(relatedFile); await DbContext.SaveChangesAsync(); } catch @@ -124,6 +216,7 @@ namespace MareSynchronosServer.Hubs var computedHashString = BitConverter.ToString(computedHash).Replace("-", ""); if (hash != computedHashString) { + Logger.LogWarning($"Computed file hash was not expected file hash. Computed: {computedHashString}, Expected {hash}"); DbContext.Remove(relatedFile); await DbContext.SaveChangesAsync(); return; @@ -135,87 +228,13 @@ namespace MareSynchronosServer.Hubs relatedFile.LastAccessTime = DateTime.Now; await DbContext.SaveChangesAsync(); Logger.LogInformation("File " + hash + " added to DB"); - return; } catch (Exception ex) { - Debug.Write(ex.Message); - } - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task GetFileSize(string hash) - { - var file = await DbContext.Files.SingleOrDefaultAsync(f => f.Hash == hash); - var forbidden = DbContext.ForbiddenUploadEntries.SingleOrDefault(f => f.Hash == hash); - var fileInfo = new FileInfo(Path.Combine(BasePath, hash)); - - var response = new DownloadFileDto - { - FileExists = file != null, - ForbiddenBy = forbidden?.ForbiddenBy ?? string.Empty, - IsForbidden = forbidden != null, - Hash = hash, - Size = fileInfo.Length - }; - - if (!fileInfo.Exists && file != null) - { - DbContext.Files.Remove(file); + Logger.LogWarning(ex, "Upload failed"); + DbContext.Remove(relatedFile); await DbContext.SaveChangesAsync(); } - - return response; - - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async IAsyncEnumerable DownloadFileAsync(string hash, [EnumeratorCancellation] CancellationToken ct) - { - Logger.LogInformation("User " + AuthenticatedUserId + " downloading file: " + hash); - - var file = DbContext.Files.SingleOrDefault(f => f.Hash == hash); - if (file == null) yield break; - file.LastAccessTime = DateTime.Now; - DbContext.Update(file); - await DbContext.SaveChangesAsync(ct); - var chunkSize = 1024 * 512; // 512kb - int readByteCount; - var buffer = new byte[chunkSize]; - - await using var fs = File.Open(Path.Combine(BasePath, hash), FileMode.Open, FileAccess.Read); - while ((readByteCount = await fs.ReadAsync(buffer, 0, chunkSize, ct)) > 0) - { - await Task.Delay(10, ct); - yield return readByteCount == chunkSize ? buffer.ToArray() : buffer.Take(readByteCount).ToArray(); - } - - Logger.LogInformation("User " + AuthenticatedUserId + " finished downloading file: " + hash); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task DeleteAllFiles() - { - Logger.LogInformation("User " + AuthenticatedUserId + " deleted all their files"); - - DbContext.CharacterData.RemoveRange(DbContext.CharacterData.Where(c => c.UserId == AuthenticatedUserId)); - await DbContext.SaveChangesAsync(); - var ownFiles = await DbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == AuthenticatedUserId).ToListAsync(); - foreach (var file in ownFiles) - { - File.Delete(Path.Combine(BasePath, file.Hash)); - } - DbContext.Files.RemoveRange(ownFiles); - await DbContext.SaveChangesAsync(); - } - - public override Task OnDisconnectedAsync(Exception exception) - { - var userId = AuthenticatedUserId; - var notUploadedFiles = DbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == userId).ToList(); - DbContext.RemoveRange(notUploadedFiles); - DbContext.SaveChanges(); - return base.OnDisconnectedAsync(exception); } } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs index 0ee6403..95b8788 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/UserHub.cs @@ -22,11 +22,142 @@ namespace MareSynchronosServer.Hubs { } + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(UserHubAPI.SendDeleteAccount)] + public async Task DeleteAccount() + { + Logger.LogInformation("User " + AuthenticatedUserId + " deleted their account"); + + string userid = AuthenticatedUserId; + var userEntry = await DbContext.Users.SingleAsync(u => u.UID == userid); + var charData = DbContext.CharacterData.Where(u => u.UserId == userid); + DbContext.RemoveRange(charData); + await DbContext.SaveChangesAsync(); + var ownPairData = DbContext.ClientPairs.Where(u => u.User.UID == userid); + DbContext.RemoveRange(ownPairData); + await DbContext.SaveChangesAsync(); + var otherPairData = DbContext.ClientPairs.Include(u => u.User).Where(u => u.OtherUser.UID == userid); + foreach (var pair in otherPairData) + { + await Clients.User(pair.User.UID) + .SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() + { + OtherUID = userid, + IsRemoved = true + }, userEntry.CharacterIdentification); + } + + DbContext.RemoveRange(otherPairData); + DbContext.Remove(userEntry); + await DbContext.SaveChangesAsync(); + + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(UserHubAPI.InvokeGetOnlineCharacters)] + public async Task> GetOnlineCharacters() + { + Logger.LogInformation("User " + AuthenticatedUserId + " requested online characters"); + + var ownUser = DbContext.Users.Single(u => u.UID == AuthenticatedUserId); + var otherUsers = await DbContext.ClientPairs + .Include(u => u.User) + .Include(u => u.OtherUser) + .Where(w => w.User == ownUser && !w.IsPaused) + .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) + .Select(e => e.OtherUser).ToListAsync(); + var otherEntries = await DbContext.ClientPairs.Include(u => u.User) + .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == ownUser && !u.IsPaused).ToListAsync(); + + await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync(UserHubAPI.OnAddOnlinePairedPlayer, ownUser.CharacterIdentification); + await Clients.All.SendAsync(UserHubAPI.OnUsersOnline, + await DbContext.Users.CountAsync(u => !string.IsNullOrEmpty(u.CharacterIdentification))); + return otherEntries.Select(e => e.User.CharacterIdentification).Distinct().ToList(); + } + + [HubMethodName(UserHubAPI.InvokeGetOnlineUsers)] public async Task GetOnlineUsers() { return await DbContext.Users.CountAsync(u => !string.IsNullOrEmpty(u.CharacterIdentification)); } + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(UserHubAPI.InvokeGetPairedClients)] + public async Task> GetPairedClients() + { + string userid = AuthenticatedUserId; + var user = GetAuthenticatedUser(); + return DbContext.ClientPairs + .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 ClientPairDto + { + IsPaused = w.IsPaused, + OtherUID = w.OtherUser.UID, + IsSynced = otherEntry != null, + IsPausedFromOthers = otherEntry?.IsPaused ?? false, + }; + }).ToList(); + } + + public override async Task OnDisconnectedAsync(Exception exception) + { + var user = DbContext.Users.SingleOrDefault(u => u.UID == AuthenticatedUserId); + if (user != null) + { + Logger.LogInformation("Disconnect from " + AuthenticatedUserId); + + var otherUsers = DbContext.ClientPairs + .Include(u => u.User) + .Include(u => u.OtherUser) + .Where(w => w.User == user && !w.IsPaused) + .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) + .Select(e => e.OtherUser).ToList(); + var otherEntries = DbContext.ClientPairs.Include(u => u.User) + .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == user && !u.IsPaused).ToList(); + await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync(UserHubAPI.OnRemoveOnlinePairedPlayer, user.CharacterIdentification); + + var outdatedCharacterData = DbContext.CharacterData.Where(v => v.UserId == user.UID); + DbContext.RemoveRange(outdatedCharacterData); + user.CharacterIdentification = null; + await DbContext.SaveChangesAsync(); + + await Clients.All.SendAsync("UsersOnline", + await DbContext.Users.CountAsync(u => !string.IsNullOrEmpty(u.CharacterIdentification))); + } + + await base.OnDisconnectedAsync(exception); + } + + [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(UserHubAPI.InvokePushCharacterDataToVisibleClients)] + public async Task PushCharacterDataToVisibleClients(CharacterCacheDto characterCache, List visibleCharacterIds) + { + Logger.LogInformation("User " + AuthenticatedUserId + " pushing character data to " + visibleCharacterIds.Count + " visible clients"); + + var uid = AuthenticatedUserId; + var entriesHavingThisUser = DbContext.ClientPairs + .Include(w => w.User) + .Include(w => w.OtherUser) + .Where(w => w.OtherUser.UID == uid && !w.IsPaused + && visibleCharacterIds.Contains(w.User.CharacterIdentification)).ToList(); + + foreach (var pair in entriesHavingThisUser) + { + var ownEntry = DbContext.ClientPairs.SingleOrDefault(w => + w.User.UID == uid && w.OtherUser.UID == pair.User.UID); + if (ownEntry == null || ownEntry.IsPaused) continue; + await Clients.User(pair.User.UID).SendAsync(UserHubAPI.OnReceiveCharacterData, characterCache, + pair.OtherUser.CharacterIdentification); + } + } + + [HubMethodName(UserHubAPI.InvokeRegister)] public async Task Register() { using var sha256 = SHA256.Create(); @@ -60,110 +191,14 @@ namespace MareSynchronosServer.Hubs return computedHash; } - private ClientPair OppositeEntry(string otherUID) => - DbContext.ClientPairs.SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == AuthenticatedUserId); - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public string GetUID() - { - return AuthenticatedUserId; - } - - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task PushCharacterDataToVisibleClients(CharacterCacheDto characterCache, List visibleCharacterIds) - { - Logger.LogInformation("User " + AuthenticatedUserId + " pushing character data to " + visibleCharacterIds.Count + " visible clients"); - - var uid = AuthenticatedUserId; - var entriesHavingThisUser = DbContext.ClientPairs - .Include(w => w.User) - .Include(w => w.OtherUser) - .Where(w => w.OtherUser.UID == uid && !w.IsPaused - && visibleCharacterIds.Contains(w.User.CharacterIdentification)).ToList(); - - foreach (var pair in entriesHavingThisUser) - { - var ownEntry = DbContext.ClientPairs.SingleOrDefault(w => - w.User.UID == uid && w.OtherUser.UID == pair.User.UID); - if (ownEntry == null || ownEntry.IsPaused) continue; - await Clients.User(pair.User.UID).SendAsync("ReceiveCharacterData", characterCache, - pair.OtherUser.CharacterIdentification); - } - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task PushCharacterData(CharacterCacheDto characterCache, List visibleCharacterIds) - { - Logger.LogInformation("User " + AuthenticatedUserId + " pushing character data"); - - var uid = AuthenticatedUserId; - var entriesHavingThisUser = DbContext.ClientPairs - .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.CharacterCache = characterCache; - existingCharacterData.Hash = characterCache.Hash; - DbContext.CharacterData.Update(existingCharacterData); - await DbContext.SaveChangesAsync(); - } - else if (existingCharacterData == null) - { - CharacterData data = new CharacterData - { - UserId = AuthenticatedUserId, - CharacterCache = characterCache, - Hash = characterCache.Hash, - JobId = characterCache.JobId - }; - await DbContext.CharacterData.AddAsync(data); - await DbContext.SaveChangesAsync(); - } - - foreach (var pair in entriesHavingThisUser) - { - var ownEntry = DbContext.ClientPairs.SingleOrDefault(w => - w.User.UID == uid && w.OtherUser.UID == pair.User.UID); - if (ownEntry == null || ownEntry.IsPaused) continue; - await Clients.User(pair.User.UID).SendAsync("ReceiveCharacterData", characterCache, - pair.OtherUser.CharacterIdentification); - } - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task> GetOnlineCharacters() - { - Logger.LogInformation("User " + AuthenticatedUserId + " requested online characters"); - - var ownUser = DbContext.Users.Single(u => u.UID == AuthenticatedUserId); - var otherUsers = await DbContext.ClientPairs - .Include(u => u.User) - .Include(u => u.OtherUser) - .Where(w => w.User == ownUser && !w.IsPaused) - .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) - .Select(e => e.OtherUser).ToListAsync(); - var otherEntries = await DbContext.ClientPairs.Include(u => u.User) - .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == ownUser && !u.IsPaused).ToListAsync(); - - await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync("AddOnlinePairedPlayer", ownUser.CharacterIdentification); - await Clients.All.SendAsync("UsersOnline", - await DbContext.Users.CountAsync(u => !string.IsNullOrEmpty(u.CharacterIdentification))); - return otherEntries.Select(e => e.User.CharacterIdentification).Distinct().ToList(); - } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(UserHubAPI.SendPairedClientAddition)] public async Task SendPairedClientAddition(string uid) { if (uid == AuthenticatedUserId) return; - - Logger.LogInformation("User " + AuthenticatedUserId + " added " + uid + " to whitelist"); + uid = uid.Trim(); + Logger.LogInformation("User " + AuthenticatedUserId + " adding " + uid + " to whitelist"); var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); var existingEntry = @@ -180,7 +215,7 @@ namespace MareSynchronosServer.Hubs await DbContext.SaveChangesAsync(); var otherEntry = OppositeEntry(uid); await Clients.User(user.UID) - .SendAsync("UpdateClientPairs", new ClientPairDto() + .SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() { OtherUID = otherUser.UID, IsPaused = false, @@ -192,12 +227,12 @@ namespace MareSynchronosServer.Hubs if (!string.IsNullOrEmpty(otherUser.CharacterIdentification)) { await Clients.User(user.UID) - .SendAsync("AddOnlinePairedPlayer", otherUser.CharacterIdentification); + .SendAsync(UserHubAPI.OnAddOnlinePairedPlayer, otherUser.CharacterIdentification); await Clients.User(otherUser.UID) - .SendAsync("AddOnlinePairedPlayer", user.CharacterIdentification); + .SendAsync(UserHubAPI.OnAddOnlinePairedPlayer, user.CharacterIdentification); } - await Clients.User(uid).SendAsync("UpdateClientPairs", + await Clients.User(uid).SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() { OtherUID = user.UID, @@ -209,46 +244,7 @@ namespace MareSynchronosServer.Hubs } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task SendPairedClientRemoval(string uid) - { - if (uid == AuthenticatedUserId) return; - - Logger.LogInformation("User " + AuthenticatedUserId + " removed " + uid + " from whitelist"); - var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); - var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); - if (otherUser == null) return; - ClientPair wl = - await DbContext.ClientPairs.SingleOrDefaultAsync(w => w.User == user && w.OtherUser == otherUser); - if (wl == null) return; - DbContext.ClientPairs.Remove(wl); - await DbContext.SaveChangesAsync(); - var otherEntry = OppositeEntry(uid); - await Clients.User(user.UID) - .SendAsync("UpdateClientPairs", new ClientPairDto() - { - OtherUID = otherUser.UID, - IsRemoved = true - }, otherUser.CharacterIdentification); - if (otherEntry != null) - { - if (!string.IsNullOrEmpty(otherUser.CharacterIdentification)) - { - await Clients.User(user.UID) - .SendAsync("RemoveOnlinePairedPlayer", otherUser.CharacterIdentification); - await Clients.User(otherUser.UID) - .SendAsync("RemoveOnlinePairedPlayer", user.CharacterIdentification); - } - await Clients.User(uid).SendAsync("UpdateClientPairs", new ClientPairDto() - { - OtherUID = user.UID, - IsPaused = otherEntry.IsPaused, - IsPausedFromOthers = false, - IsSynced = false - }, user.CharacterIdentification); - } - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] + [HubMethodName(UserHubAPI.SendPairedClientPauseChange)] public async Task SendPairedClientPauseChange(string uid, bool isPaused) { if (uid == AuthenticatedUserId) return; @@ -264,7 +260,7 @@ namespace MareSynchronosServer.Hubs var otherEntry = OppositeEntry(uid); await Clients.User(user.UID) - .SendAsync("UpdateClientPairs", new ClientPairDto() + .SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() { OtherUID = otherUser.UID, IsPaused = isPaused, @@ -273,7 +269,7 @@ namespace MareSynchronosServer.Hubs }, otherUser.CharacterIdentification); if (otherEntry != null) { - await Clients.User(uid).SendAsync("UpdateClientPairs", new ClientPairDto() + await Clients.User(uid).SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() { OtherUID = user.UID, IsPaused = otherEntry.IsPaused, @@ -284,85 +280,47 @@ namespace MareSynchronosServer.Hubs } [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task> GetPairedClients() + [HubMethodName(UserHubAPI.SendPairedClientRemoval)] + public async Task SendPairedClientRemoval(string uid) { - string userid = AuthenticatedUserId; - var user = GetAuthenticatedUser(); - return DbContext.ClientPairs - .Include(u => u.OtherUser) - .Include(u => u.User) - .Where(w => w.User.UID == userid) - .ToList() - .Select(w => + if (uid == AuthenticatedUserId) return; + + Logger.LogInformation("User " + AuthenticatedUserId + " removed " + uid + " from whitelist"); + var user = await DbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId); + var otherUser = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid); + if (otherUser == null) return; + ClientPair wl = + await DbContext.ClientPairs.SingleOrDefaultAsync(w => w.User == user && w.OtherUser == otherUser); + if (wl == null) return; + DbContext.ClientPairs.Remove(wl); + await DbContext.SaveChangesAsync(); + var otherEntry = OppositeEntry(uid); + await Clients.User(user.UID) + .SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() { - var otherEntry = OppositeEntry(w.OtherUser.UID); - return new ClientPairDto - { - IsPaused = w.IsPaused, - OtherUID = w.OtherUser.UID, - IsSynced = otherEntry != null, - IsPausedFromOthers = otherEntry?.IsPaused ?? false, - }; - }).ToList(); - } - - [Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)] - public async Task DeleteAccount() - { - Logger.LogInformation("User " + AuthenticatedUserId + " deleted their account"); - - string userid = AuthenticatedUserId; - var userEntry = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == userid); - var charData = DbContext.CharacterData.Where(u => u.UserId == userid); - DbContext.RemoveRange(charData); - await DbContext.SaveChangesAsync(); - var ownPairData = DbContext.ClientPairs.Where(u => u.User.UID == userid); - DbContext.RemoveRange(ownPairData); - await DbContext.SaveChangesAsync(); - var otherPairData = DbContext.ClientPairs.Include(u => u.User).Where(u => u.OtherUser.UID == userid); - foreach (var pair in otherPairData) + OtherUID = otherUser.UID, + IsRemoved = true + }, otherUser.CharacterIdentification); + if (otherEntry != null) { - await Clients.User(pair.User.UID) - .SendAsync("UpdateClientPairs", new ClientPairDto() - { - OtherUID = userid, - IsRemoved = true - }, userEntry.CharacterIdentification); + if (!string.IsNullOrEmpty(otherUser.CharacterIdentification)) + { + await Clients.User(user.UID) + .SendAsync(UserHubAPI.OnRemoveOnlinePairedPlayer, otherUser.CharacterIdentification); + await Clients.User(otherUser.UID) + .SendAsync(UserHubAPI.OnRemoveOnlinePairedPlayer, user.CharacterIdentification); + } + await Clients.User(uid).SendAsync(UserHubAPI.OnUpdateClientPairs, new ClientPairDto() + { + OtherUID = user.UID, + IsPaused = otherEntry.IsPaused, + IsPausedFromOthers = false, + IsSynced = false + }, user.CharacterIdentification); } - - DbContext.RemoveRange(otherPairData); - DbContext.Remove(userEntry); - await DbContext.SaveChangesAsync(); - } - public override async Task OnDisconnectedAsync(Exception exception) - { - var user = DbContext.Users.SingleOrDefault(u => u.UID == AuthenticatedUserId); - if (user != null) - { - Logger.LogInformation("Disconnect from " + AuthenticatedUserId); - - var otherUsers = DbContext.ClientPairs - .Include(u => u.User) - .Include(u => u.OtherUser) - .Where(w => w.User == user && !w.IsPaused) - .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) - .Select(e => e.OtherUser).ToList(); - var otherEntries = DbContext.ClientPairs.Include(u => u.User) - .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == user && !u.IsPaused).ToList(); - await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync("RemoveOnlinePairedPlayer", user.CharacterIdentification); - - var outdatedCharacterData = DbContext.CharacterData.Where(v => v.UserId == user.UID); - DbContext.RemoveRange(outdatedCharacterData); - user.CharacterIdentification = null; - await DbContext.SaveChangesAsync(); - - await Clients.All.SendAsync("UsersOnline", - await DbContext.Users.CountAsync(u => !string.IsNullOrEmpty(u.CharacterIdentification))); - } - - await base.OnDisconnectedAsync(exception); - } + private ClientPair OppositeEntry(string otherUID) => + DbContext.ClientPairs.SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == AuthenticatedUserId); } } diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index f9d490d..50dee85 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -1,4 +1,5 @@ using System; +using MareSynchronos.API; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -85,16 +86,16 @@ namespace MareSynchronosServer app.UseEndpoints(endpoints => { - endpoints.MapHub("/heartbeat", options => + endpoints.MapHub(ConnectionHubAPI.Path, options => { options.Transports = HttpTransportType.WebSockets; }); - endpoints.MapHub("/user", options => + endpoints.MapHub(UserHubAPI.Path, options => { options.Transports = HttpTransportType.WebSockets; }); - endpoints.MapHub("/admin", options => options.Transports = HttpTransportType.WebSockets); - endpoints.MapHub("/files", options => + endpoints.MapHub(AdminHubAPI.Path, options => options.Transports = HttpTransportType.WebSockets); + endpoints.MapHub(FilesHubAPI.Path, options => { options.ApplicationMaxBufferSize = long.MaxValue; options.TransportMaxBufferSize = long.MaxValue;