diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Admin.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Admin.cs index 4600284..605cdb1 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Admin.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Admin.cs @@ -16,8 +16,8 @@ namespace MareSynchronosServer.Hubs 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(); + private List OnlineAdmins => _dbContext.Users.Where(u => (u.IsModerator || u.IsAdmin)).Select(u => u.UID).ToList(); + [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] [HubMethodName(Api.SendAdminChangeModeratorStatus)] public async Task ChangeModeratorStatus(string uid, bool isModerator) @@ -101,13 +101,14 @@ namespace MareSynchronosServer.Hubs { if (!IsModerator) return null; - return await _dbContext.Users.AsNoTracking().Where(b => !string.IsNullOrEmpty(b.CharacterIdentification)).Select(b => new OnlineUserDto + var users = await _dbContext.Users.AsNoTracking().ToListAsync().ConfigureAwait(false); + return users.Where(c => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(c.UID))).Select(b => new OnlineUserDto { - CharacterNameHash = b.CharacterIdentification, + CharacterNameHash = _clientIdentService.GetCharacterIdentForUid(b.UID), UID = b.UID, IsModerator = b.IsModerator, IsAdmin = b.IsAdmin - }).ToListAsync().ConfigureAwait(false); + }).ToList(); } [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] @@ -134,11 +135,10 @@ namespace MareSynchronosServer.Hubs await _dbContext.SaveChangesAsync().ConfigureAwait(false); await Clients.Users(OnlineAdmins).SendAsync(Api.OnAdminUpdateOrAddBannedUser, dto).ConfigureAwait(false); - var bannedUser = - await _dbContext.Users.SingleOrDefaultAsync(u => u.CharacterIdentification == dto.CharacterHash).ConfigureAwait(false); - if (bannedUser != null) + var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash); + if (!string.IsNullOrEmpty(bannedUser)) { - await Clients.User(bannedUser.UID).SendAsync(Api.OnAdminForcedReconnect).ConfigureAwait(false); + await Clients.User(bannedUser).SendAsync(Api.OnAdminForcedReconnect).ConfigureAwait(false); } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Files.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Files.cs index 1028c9b..3a89f58 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Files.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Files.cs @@ -76,7 +76,7 @@ namespace MareSynchronosServer.Hubs IsForbidden = forbiddenFile != null, Hash = hash.Key, Size = hash.Value, - Url = new Uri(cdnFullUri, hash.Key.ToUpperInvariant()).ToString() + Url = new Uri(_cdnFullUri, hash.Key.ToUpperInvariant()).ToString() }); } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs index d71adf6..74ea67d 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs @@ -21,9 +21,9 @@ namespace MareSynchronosServer.Hubs { _logger.LogInformation("User {AuthenticatedUserId} deleted their account", AuthenticatedUserId); - string userid = AuthenticatedUserId; var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == userid).ConfigureAwait(false); + var charaIdent = _clientIdentService.GetCharacterIdentForUid(userid); var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == userid).ToListAsync().ConfigureAwait(false); var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == userid).ConfigureAwait(false); var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == userid).ConfigureAwait(false); @@ -52,7 +52,7 @@ namespace MareSynchronosServer.Hubs { OtherUID = userid, IsRemoved = true - }, userEntry.CharacterIdentification).ConfigureAwait(false); + }, charaIdent).ConfigureAwait(false); } _mareMetrics.DecGauge(MetricsAPI.GaugePairs, ownPairData.Count + otherPairData.Count); @@ -77,14 +77,18 @@ namespace MareSynchronosServer.Hubs .Include(u => u.User) .Include(u => u.OtherUser) .Where(w => w.User.UID == ownUser.UID && !w.IsPaused) - .Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) + //.Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification)) .Select(e => e.OtherUser).ToListAsync().ConfigureAwait(false); + var otherOnlineUsers = + otherUsers.Where(u => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(u.UID))); var otherEntries = await _dbContext.ClientPairs.AsNoTracking() .Include(u => u.User) - .Where(u => otherUsers.Any(e => e == u.User) && u.OtherUser == ownUser && !u.IsPaused).ToListAsync().ConfigureAwait(false); + .Where(u => otherOnlineUsers.Any(e => e == u.User) && u.OtherUser == ownUser && !u.IsPaused) + .ToListAsync().ConfigureAwait(false); + var ownIdent = _clientIdentService.GetCharacterIdentForUid(ownUser.UID); - await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync(Api.OnUserAddOnlinePairedPlayer, ownUser.CharacterIdentification).ConfigureAwait(false); - return otherEntries.Select(e => e.User.CharacterIdentification).Distinct().ToList(); + await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync(Api.OnUserAddOnlinePairedPlayer, ownIdent).ConfigureAwait(false); + return otherEntries.Select(e => _clientIdentService.GetCharacterIdentForUid(e.User.UID)).Distinct().ToList(); } [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] @@ -152,12 +156,14 @@ namespace MareSynchronosServer.Hubs userToOther.UserUID == user.UID && !userToOther.IsPaused && !otherToUser.IsPaused - && visibleCharacterIds.Contains(userToOther.OtherUser.CharacterIdentification) select otherToUser.UserUID; var otherEntries = await query.ToListAsync().ConfigureAwait(false); + otherEntries = + otherEntries.Where(c => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(c))).ToList(); + var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId); - await Clients.Users(otherEntries).SendAsync(Api.OnUserReceiveCharacterData, characterCache, user.CharacterIdentification).ConfigureAwait(false); + await Clients.Users(otherEntries).SendAsync(Api.OnUserReceiveCharacterData, characterCache, ownIdent).ConfigureAwait(false); _mareMetrics.IncCounter(MetricsAPI.CounterUserPushData); _mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, otherEntries.Count); @@ -199,6 +205,7 @@ namespace MareSynchronosServer.Hubs }, string.Empty).ConfigureAwait(false); if (otherEntry != null) { + var userIdent = _clientIdentService.GetCharacterIdentForUid(user.UID); await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto() { @@ -207,14 +214,15 @@ namespace MareSynchronosServer.Hubs IsPaused = otherEntry.IsPaused, IsPausedFromOthers = false, IsSynced = true - }, user.CharacterIdentification).ConfigureAwait(false); + }, userIdent).ConfigureAwait(false); - if (!string.IsNullOrEmpty(otherUser.CharacterIdentification)) + var otherIdent = _clientIdentService.GetCharacterIdentForUid(otherUser.UID); + if (!string.IsNullOrEmpty(otherIdent)) { await Clients.User(user.UID) - .SendAsync(Api.OnUserAddOnlinePairedPlayer, otherUser.CharacterIdentification).ConfigureAwait(false); + .SendAsync(Api.OnUserAddOnlinePairedPlayer, otherIdent).ConfigureAwait(false); await Clients.User(otherUser.UID) - .SendAsync(Api.OnUserAddOnlinePairedPlayer, user.CharacterIdentification).ConfigureAwait(false); + .SendAsync(Api.OnUserAddOnlinePairedPlayer, userIdent).ConfigureAwait(false); } } @@ -233,8 +241,8 @@ namespace MareSynchronosServer.Hubs pair.IsPaused = isPaused; _dbContext.Update(pair); await _dbContext.SaveChangesAsync().ConfigureAwait(false); - var selfCharaIdent = (await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false)).CharacterIdentification; - var otherCharaIdent = (await _dbContext.Users.SingleAsync(u => u.UID == otherUserUid).ConfigureAwait(false)).CharacterIdentification; + var selfCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId); + var otherCharaIdent = _clientIdentService.GetCharacterIdentForUid(pair.OtherUserUID); var otherEntry = OppositeEntry(otherUserUid); await Clients.User(AuthenticatedUserId) @@ -282,27 +290,29 @@ namespace MareSynchronosServer.Hubs _dbContext.ClientPairs.Remove(wl); await _dbContext.SaveChangesAsync().ConfigureAwait(false); var otherEntry = OppositeEntry(uid); + var otherIdent = _clientIdentService.GetCharacterIdentForUid(otherUser.UID); await Clients.User(sender.UID) .SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto() { OtherUID = otherUser.UID, IsRemoved = true - }, otherUser.CharacterIdentification).ConfigureAwait(false); + }, otherIdent).ConfigureAwait(false); if (otherEntry != null) { - if (!string.IsNullOrEmpty(otherUser.CharacterIdentification)) + if (!string.IsNullOrEmpty(otherIdent)) { + var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId); await Clients.User(sender.UID) - .SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherUser.CharacterIdentification).ConfigureAwait(false); + .SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherIdent).ConfigureAwait(false); await Clients.User(otherUser.UID) - .SendAsync(Api.OnUserRemoveOnlinePairedPlayer, sender.CharacterIdentification).ConfigureAwait(false); + .SendAsync(Api.OnUserRemoveOnlinePairedPlayer, ownIdent).ConfigureAwait(false); await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto() { OtherUID = sender.UID, IsPaused = otherEntry.IsPaused, IsPausedFromOthers = false, IsSynced = false - }, sender.CharacterIdentification).ConfigureAwait(false); + }, ownIdent).ConfigureAwait(false); } } diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs index 4305ee8..fa0646a 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs @@ -3,11 +3,13 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using MareSynchronos.API; +using MareSynchronosServer.Services; using MareSynchronosShared.Authentication; using MareSynchronosShared.Data; using MareSynchronosShared.Metrics; using MareSynchronosShared.Models; using MareSynchronosShared.Protos; +using MareSynchronosShared.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SignalR; @@ -15,133 +17,134 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -namespace MareSynchronosServer.Hubs +namespace MareSynchronosServer.Hubs; + +public partial class MareHub : Hub { - public partial class MareHub : Hub + private readonly MareMetrics _mareMetrics; + private readonly AuthService.AuthServiceClient _authServiceClient; + private readonly FileService.FileServiceClient _fileServiceClient; + private readonly SystemInfoService _systemInfoService; + private readonly IHttpContextAccessor _contextAccessor; + private readonly IClientIdentificationService _clientIdentService; + private readonly ILogger _logger; + private readonly MareDbContext _dbContext; + private readonly Uri _cdnFullUri; + public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient, + MareDbContext mareDbContext, ILogger logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor, + IClientIdentificationService clientIdentService) { - private readonly MareMetrics _mareMetrics; - private readonly AuthService.AuthServiceClient _authServiceClient; - private readonly FileService.FileServiceClient _fileServiceClient; - private readonly SystemInfoService _systemInfoService; - private readonly IHttpContextAccessor contextAccessor; - private readonly ILogger _logger; - private readonly MareDbContext _dbContext; - private readonly Uri cdnFullUri; - public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient, - MareDbContext mareDbContext, ILogger logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor) + _mareMetrics = mareMetrics; + _authServiceClient = authServiceClient; + _fileServiceClient = fileServiceClient; + _systemInfoService = systemInfoService; + _cdnFullUri = new Uri(configuration.GetRequiredSection("MareSynchronos").GetValue("CdnFullUrl")); + _contextAccessor = contextAccessor; + _clientIdentService = clientIdentService; + _logger = logger; + _dbContext = mareDbContext; + } + + [HubMethodName(Api.InvokeHeartbeat)] + [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] + public async Task Heartbeat(string characterIdentification) + { + _mareMetrics.IncCounter(MetricsAPI.CounterInitializedConnections); + + var userId = Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; + + _logger.LogInformation("Connection from {userId}, CI: {characterIdentification}", userId, characterIdentification); + + await Clients.Caller.SendAsync(Api.OnUpdateSystemInfo, _systemInfoService.SystemInfoDto).ConfigureAwait(false); + + var isBanned = await _dbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == characterIdentification).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(userId) && !isBanned && !string.IsNullOrEmpty(characterIdentification)) { - _mareMetrics = mareMetrics; - _authServiceClient = authServiceClient; - _fileServiceClient = fileServiceClient; - _systemInfoService = systemInfoService; - cdnFullUri = new Uri(configuration.GetRequiredSection("MareSynchronos").GetValue("CdnFullUrl")); - this.contextAccessor = contextAccessor; - _logger = logger; - _dbContext = mareDbContext; - } - - [HubMethodName(Api.InvokeHeartbeat)] - [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] - public async Task Heartbeat(string characterIdentification) - { - _mareMetrics.IncCounter(MetricsAPI.CounterInitializedConnections); - - var userId = Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; - - _logger.LogInformation("Connection from {userId}, CI: {characterIdentification}", userId, characterIdentification); - - await Clients.Caller.SendAsync(Api.OnUpdateSystemInfo, _systemInfoService.SystemInfoDto).ConfigureAwait(false); - - var isBanned = await _dbContext.BannedUsers.AsNoTracking().AnyAsync(u => u.CharacterIdentification == characterIdentification).ConfigureAwait(false); - - if (!string.IsNullOrEmpty(userId) && !isBanned && !string.IsNullOrEmpty(characterIdentification)) + var user = (await _dbContext.Users.SingleAsync(u => u.UID == userId).ConfigureAwait(false)); + var existingIdent = _clientIdentService.GetCharacterIdentForUid(userId); + if (!string.IsNullOrEmpty(existingIdent) && characterIdentification != existingIdent) { - var user = (await _dbContext.Users.SingleAsync(u => u.UID == userId).ConfigureAwait(false)); - if (!string.IsNullOrEmpty(user.CharacterIdentification) && characterIdentification != user.CharacterIdentification) + return new ConnectionDto() { - return new ConnectionDto() - { - ServerVersion = Api.Version - }; - } - else if (string.IsNullOrEmpty(user.CharacterIdentification)) - { - _mareMetrics.IncGauge(MetricsAPI.GaugeAuthorizedConnections); - } - - user.LastLoggedIn = DateTime.UtcNow; - user.CharacterIdentification = characterIdentification; - await _dbContext.SaveChangesAsync().ConfigureAwait(false); - - return new ConnectionDto - { - ServerVersion = Api.Version, - UID = string.IsNullOrEmpty(user.Alias) ? user.UID : user.Alias, - IsModerator = user.IsModerator, - IsAdmin = user.IsAdmin + ServerVersion = Api.Version }; } - return new ConnectionDto() + user.LastLoggedIn = DateTime.UtcNow; + _clientIdentService.MarkUserOnline(user.UID, characterIdentification); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + + return new ConnectionDto { - ServerVersion = Api.Version + ServerVersion = Api.Version, + UID = string.IsNullOrEmpty(user.Alias) ? user.UID : user.Alias, + IsModerator = user.IsModerator, + IsAdmin = user.IsAdmin }; } - public override async Task OnConnectedAsync() + return new ConnectionDto() { - _logger.LogInformation("Connection from {ip}", contextAccessor.GetIpAddress()); - _mareMetrics.IncGauge(MetricsAPI.GaugeConnections); - await base.OnConnectedAsync().ConfigureAwait(false); - } - - public override async Task OnDisconnectedAsync(Exception exception) - { - _mareMetrics.DecGauge(MetricsAPI.GaugeConnections); - - var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false); - if (user != null && !string.IsNullOrEmpty(user.CharacterIdentification)) - { - _mareMetrics.DecGauge(MetricsAPI.GaugeAuthorizedConnections); - - _logger.LogInformation("Disconnect from {id}", AuthenticatedUserId); - - var query = - from userToOther in _dbContext.ClientPairs - join otherToUser in _dbContext.ClientPairs - on new - { - user = userToOther.UserUID, - other = userToOther.OtherUserUID - - } equals new - { - user = otherToUser.OtherUserUID, - other = otherToUser.UserUID - } - where - userToOther.UserUID == user.UID - && !userToOther.IsPaused - && !otherToUser.IsPaused - select otherToUser.UserUID; - var otherEntries = await query.ToListAsync().ConfigureAwait(false); - - await Clients.Users(otherEntries).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, user.CharacterIdentification).ConfigureAwait(false); - - _dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == user.UID)); - - user.CharacterIdentification = null; - await _dbContext.SaveChangesAsync().ConfigureAwait(false); - } - - await base.OnDisconnectedAsync(exception).ConfigureAwait(false); - } - - protected string AuthenticatedUserId => Context.User?.Claims?.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? "Unknown"; - - protected async Task GetAuthenticatedUserUntrackedAsync() - { - return await _dbContext.Users.AsNoTrackingWithIdentityResolution().SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false); - } + ServerVersion = Api.Version + }; } -} + + public override async Task OnConnectedAsync() + { + _logger.LogInformation("Connection from {ip}", _contextAccessor.GetIpAddress()); + _mareMetrics.IncGauge(MetricsAPI.GaugeConnections); + await base.OnConnectedAsync().ConfigureAwait(false); + } + + public override async Task OnDisconnectedAsync(Exception exception) + { + _mareMetrics.DecGauge(MetricsAPI.GaugeConnections); + + var userCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId); + + if (!string.IsNullOrEmpty(userCharaIdent)) + { + var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == AuthenticatedUserId)!.ConfigureAwait(false); + _mareMetrics.DecGauge(MetricsAPI.GaugeAuthorizedConnections); + + _logger.LogInformation("Disconnect from {id}", AuthenticatedUserId); + + var query = + from userToOther in _dbContext.ClientPairs + join otherToUser in _dbContext.ClientPairs + on new + { + user = userToOther.UserUID, + other = userToOther.OtherUserUID + + } equals new + { + user = otherToUser.OtherUserUID, + other = otherToUser.UserUID + } + where + userToOther.UserUID == user.UID + && !userToOther.IsPaused + && !otherToUser.IsPaused + select otherToUser.UserUID; + var otherEntries = await query.ToListAsync().ConfigureAwait(false); + + await Clients.Users(otherEntries).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, userCharaIdent).ConfigureAwait(false); + + _dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == user.UID)); + + _clientIdentService.MarkUserOffline(user.UID); + await _dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + await base.OnDisconnectedAsync(exception).ConfigureAwait(false); + } + + protected string AuthenticatedUserId => Context.User?.Claims?.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? "Unknown"; + + protected async Task GetAuthenticatedUserUntrackedAsync() + { + return await _dbContext.Users.AsNoTrackingWithIdentityResolution().SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosServer/Program.cs b/MareSynchronosServer/MareSynchronosServer/Program.cs index 01228a6..a023e6a 100644 --- a/MareSynchronosServer/MareSynchronosServer/Program.cs +++ b/MareSynchronosServer/MareSynchronosServer/Program.cs @@ -29,11 +29,6 @@ namespace MareSynchronosServer context.SaveChanges(); // clean up residuals - var users = context.Users; - foreach (var user in users) - { - user.CharacterIdentification = null; - } var looseFiles = context.Files.Where(f => f.Uploaded == false); var unfinishedRegistrations = context.LodeStoneAuth.Where(c => c.StartedAt != null); context.RemoveRange(unfinishedRegistrations); diff --git a/MareSynchronosServer/MareSynchronosServer/SystemInfoService.cs b/MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs similarity index 84% rename from MareSynchronosServer/MareSynchronosServer/SystemInfoService.cs rename to MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs index b0dcfe6..e4bdfb6 100644 --- a/MareSynchronosServer/MareSynchronosServer/SystemInfoService.cs +++ b/MareSynchronosServer/MareSynchronosServer/Services/SystemInfoService.cs @@ -1,32 +1,32 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MareSynchronos.API; using MareSynchronosServer.Hubs; using MareSynchronosShared.Data; using MareSynchronosShared.Metrics; -using MareSynchronosShared.Models; -using MareSynchronosShared.Protos; +using MareSynchronosShared.Services; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace MareSynchronosServer; +namespace MareSynchronosServer.Services; public class SystemInfoService : IHostedService, IDisposable { private readonly MareMetrics _mareMetrics; + private readonly IClientIdentificationService clientIdentService; private readonly IServiceProvider _services; private readonly ILogger _logger; private readonly IHubContext _hubContext; private Timer _timer; public SystemInfoDto SystemInfoDto { get; private set; } = new(); - public SystemInfoService(MareMetrics mareMetrics, IServiceProvider services, ILogger logger, IHubContext hubContext) + public SystemInfoService(MareMetrics mareMetrics, IClientIdentificationService clientIdentService, IServiceProvider services, ILogger logger, IHubContext hubContext) { _mareMetrics = mareMetrics; + this.clientIdentService = clientIdentService; _services = services; _logger = logger; _hubContext = hubContext; @@ -56,8 +56,6 @@ public class SystemInfoService : IHostedService, IDisposable using var scope = _services.CreateScope(); using var db = scope.ServiceProvider.GetService()!; - var users = db.Users.Count(c => c.CharacterIdentification != null); - SystemInfoDto = new SystemInfoDto() { CacheUsage = 0, @@ -65,7 +63,7 @@ public class SystemInfoService : IHostedService, IDisposable RAMUsage = 0, NetworkIn = 0, NetworkOut = 0, - OnlineUsers = users, + OnlineUsers = clientIdentService.GetOnlineUsers(), UploadedFiles = 0 }; diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index 10476d3..04ed5e1 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -19,6 +19,8 @@ using Grpc.Net.Client.Configuration; using Prometheus; using MareSynchronosShared.Metrics; using System.Collections.Generic; +using MareSynchronosServer.Services; +using MareSynchronosShared.Services; namespace MareSynchronosServer { @@ -101,18 +103,18 @@ namespace MareSynchronosServer options.EnableThreadSafetyChecks(false); }, mareConfig.GetValue("DbContextPoolSize", 1024)); + services.AddHostedService(provider => provider.GetService()); services.AddAuthentication(options => - { - options.DefaultScheme = SecretKeyGrpcAuthenticationHandler.AuthScheme; - }) - .AddScheme(SecretKeyGrpcAuthenticationHandler.AuthScheme, options => { }); + { + options.DefaultScheme = SecretKeyGrpcAuthenticationHandler.AuthScheme; + }).AddScheme(SecretKeyGrpcAuthenticationHandler.AuthScheme, _ => { }); services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); services.AddSingleton(); - var signalRserviceBuilder = services.AddSignalR(hubOptions => + var signalRServiceBuilder = services.AddSignalR(hubOptions => { hubOptions.MaximumReceiveMessageSize = long.MaxValue; hubOptions.EnableDetailedErrors = true; @@ -120,13 +122,28 @@ namespace MareSynchronosServer hubOptions.StreamBufferCapacity = 200; hubOptions.AddFilter(); }); + + // add redis related options var redis = mareConfig.GetValue("RedisConnectionString", string.Empty); if (!string.IsNullOrEmpty(redis)) { - signalRserviceBuilder.AddStackExchangeRedis(redis, options => + signalRServiceBuilder.AddStackExchangeRedis(redis, options => { options.Configuration.ChannelPrefix = "MareSynchronos"; }); + + services.AddStackExchangeRedisCache(opt => + { + opt.Configuration = redis; + opt.InstanceName = "MareSynchronos"; + }); + services.AddSingleton(); + services.AddHostedService(p => p.GetService()); + } + else + { + services.AddSingleton(); + services.AddHostedService(p => p.GetService()); } } diff --git a/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs b/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs index 30e7d4d..9a2ce51 100644 --- a/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs +++ b/MareSynchronosServer/MareSynchronosServices/Discord/DiscordBot.cs @@ -14,6 +14,7 @@ using MareSynchronosServices.Authentication; using MareSynchronosShared.Data; using MareSynchronosShared.Metrics; using MareSynchronosShared.Models; +using MareSynchronosShared.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -26,6 +27,7 @@ public class DiscordBot : IHostedService { private readonly CleanupService cleanupService; private readonly MareMetrics metrics; + private readonly IClientIdentificationService clientService; private readonly IServiceProvider services; private readonly IConfiguration _configuration; private readonly ILogger logger; @@ -44,10 +46,11 @@ public class DiscordBot : IHostedService private SemaphoreSlim semaphore; - public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IServiceProvider services, IConfiguration configuration, ILogger logger) + public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IClientIdentificationService clientService, IServiceProvider services, IConfiguration configuration, ILogger logger) { this.cleanupService = cleanupService; this.metrics = metrics; + this.clientService = clientService; this.services = services; _configuration = configuration.GetRequiredSection("MareSynchronos"); this.logger = logger; @@ -687,13 +690,7 @@ public class DiscordBot : IHostedService updateStatusCts = new(); while (!updateStatusCts.IsCancellationRequested) { - await using var scope = services.CreateAsyncScope(); - await using (var db = scope.ServiceProvider.GetRequiredService()) - { - var users = db.Users.Count(c => c.CharacterIdentification != null); - await discordClient.SetActivityAsync(new Game("Mare for " + users + " Users")).ConfigureAwait(false); - } - + await discordClient.SetActivityAsync(new Game("Mare for " + clientService.GetOnlineUsers() + " Users")).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false); } } diff --git a/MareSynchronosServer/MareSynchronosServices/Startup.cs b/MareSynchronosServer/MareSynchronosServices/Startup.cs index b480130..e7914e7 100644 --- a/MareSynchronosServer/MareSynchronosServices/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServices/Startup.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Prometheus; using System.Collections.Generic; +using MareSynchronosShared.Services; namespace MareSynchronosServices; @@ -49,6 +50,24 @@ public class Startup services.AddHostedService(provider => provider.GetService()); services.AddHostedService(); services.AddGrpc(); + + // add redis related options + var redis = Configuration.GetSection("MareSynchronos").GetValue("RedisConnectionString", string.Empty); + if (!string.IsNullOrEmpty(redis)) + { + services.AddStackExchangeRedisCache(opt => + { + opt.Configuration = redis; + opt.InstanceName = "MareSynchronos"; + }); + services.AddSingleton(); + services.AddHostedService(p => p.GetService()); + } + else + { + services.AddSingleton(); + services.AddHostedService(p => p.GetService()); + } } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs b/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs index e677caf..f3f50cf 100644 --- a/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs +++ b/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs @@ -45,7 +45,7 @@ public class MareDbContext : DbContext { modelBuilder.Entity().ToTable("auth"); modelBuilder.Entity().ToTable("users"); - modelBuilder.Entity().HasIndex(c => c.CharacterIdentification); + //modelBuilder.Entity().HasIndex(c => c.CharacterIdentification); modelBuilder.Entity().ToTable("file_caches"); modelBuilder.Entity().HasIndex(c => c.UploaderUID); modelBuilder.Entity().ToTable("client_pairs"); diff --git a/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj b/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj index 2964410..7f04433 100644 --- a/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj +++ b/MareSynchronosServer/MareSynchronosShared/MareSynchronosShared.csproj @@ -30,6 +30,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/MareSynchronosServer/MareSynchronosShared/Models/User.cs b/MareSynchronosServer/MareSynchronosShared/Models/User.cs index c1fa656..9a0efa7 100644 --- a/MareSynchronosServer/MareSynchronosShared/Models/User.cs +++ b/MareSynchronosServer/MareSynchronosShared/Models/User.cs @@ -7,8 +7,8 @@ namespace MareSynchronosShared.Models [Key] [MaxLength(10)] public string UID { get; set; } - [MaxLength(100)] - public string CharacterIdentification { get; set; } + //[MaxLength(100)] + //public string CharacterIdentification { get; set; } [Timestamp] public byte[] Timestamp { get; set; } diff --git a/MareSynchronosServer/MareSynchronosShared/Services/BaseClientIdentificationService.cs b/MareSynchronosServer/MareSynchronosShared/Services/BaseClientIdentificationService.cs new file mode 100644 index 0000000..281e235 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Services/BaseClientIdentificationService.cs @@ -0,0 +1,62 @@ +using System.Collections.Concurrent; +using MareSynchronosShared.Metrics; + +namespace MareSynchronosShared.Services; + +public abstract class BaseClientIdentificationService : IClientIdentificationService +{ + private readonly MareMetrics metrics; + protected ConcurrentDictionary OnlineClients = new(); + protected BaseClientIdentificationService(MareMetrics metrics) + { + this.metrics = metrics; + } + + public virtual int GetOnlineUsers() + { + return OnlineClients.Count; + } + + public string? GetUidForCharacterIdent(string characterIdent) + { + var result = OnlineClients.SingleOrDefault(u => + string.Compare(u.Value, characterIdent, StringComparison.InvariantCultureIgnoreCase) == 0); + return result.Equals(new KeyValuePair()) ? null : result.Key; + } + + public virtual string? GetCharacterIdentForUid(string uid) + { + if (!OnlineClients.TryGetValue(uid, out var result)) + { + return null; + } + + return result; + } + + public virtual void MarkUserOnline(string uid, string charaIdent) + { + OnlineClients[uid] = charaIdent; + metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count); + } + + public virtual void MarkUserOffline(string uid) + { + if (OnlineClients.TryRemove(uid, out _)) + { + metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public virtual Task StopAsync(CancellationToken cancellationToken) + { + metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, 0); + OnlineClients = new(); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Services/DistributedClientIdentificationService.cs b/MareSynchronosServer/MareSynchronosShared/Services/DistributedClientIdentificationService.cs new file mode 100644 index 0000000..a8ca046 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Services/DistributedClientIdentificationService.cs @@ -0,0 +1,57 @@ +using System.Text; +using MareSynchronosShared.Metrics; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using StackExchange.Redis; + +namespace MareSynchronosShared.Services; + +public class DistributedClientIdentificationService : BaseClientIdentificationService +{ + private readonly IDistributedCache distributedCache; + private readonly IConfiguration configuration; + private const string RedisPrefix = "uidcache:"; + + public DistributedClientIdentificationService(MareMetrics metrics, IDistributedCache distributedCache, IConfiguration configuration) : base(metrics) + { + this.distributedCache = distributedCache; + this.configuration = configuration.GetSection("MareSynchronos"); + } + + public override int GetOnlineUsers() + { + var redis = configuration.GetValue("RedisConnectionString"); + var conn = ConnectionMultiplexer.Connect(redis); + var endpoint = conn.GetEndPoints().First(); + return conn.GetServer(endpoint).Keys(pattern: RedisPrefix + "*").Count(); + } + + public override string? GetCharacterIdentForUid(string uid) + { + var localIdent = base.GetCharacterIdentForUid(uid); + if (localIdent != null) return localIdent; + var cachedIdent = distributedCache.Get(RedisPrefix + uid); + return cachedIdent == null ? null : Encoding.UTF8.GetString(cachedIdent); + } + + public override void MarkUserOffline(string uid) + { + base.MarkUserOffline(uid); + distributedCache.Remove(RedisPrefix + uid); + } + + public override void MarkUserOnline(string uid, string charaIdent) + { + base.MarkUserOnline(uid, charaIdent); + distributedCache.Set(RedisPrefix + uid, Encoding.UTF8.GetBytes(charaIdent)); + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + foreach (var uid in OnlineClients) + { + distributedCache.Remove(RedisPrefix + uid.Key); + } + return base.StopAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Services/IClientIdentificationService.cs b/MareSynchronosServer/MareSynchronosShared/Services/IClientIdentificationService.cs new file mode 100644 index 0000000..b31a7ee --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Services/IClientIdentificationService.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Hosting; + +namespace MareSynchronosShared.Services; + +public interface IClientIdentificationService : IHostedService +{ + int GetOnlineUsers(); + string? GetUidForCharacterIdent(string characterIdent); + string? GetCharacterIdentForUid(string uid); + void MarkUserOnline(string uid, string charaIdent); + void MarkUserOffline(string uid); +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Services/LocalClientIdentificationService.cs b/MareSynchronosServer/MareSynchronosShared/Services/LocalClientIdentificationService.cs new file mode 100644 index 0000000..2572f55 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Services/LocalClientIdentificationService.cs @@ -0,0 +1,10 @@ +using MareSynchronosShared.Metrics; + +namespace MareSynchronosShared.Services; + +public class LocalClientIdentificationService : BaseClientIdentificationService +{ + public LocalClientIdentificationService(MareMetrics metrics) : base(metrics) + { + } +} \ No newline at end of file