add redis for character identification

This commit is contained in:
rootdarkarchon
2022-09-13 11:45:09 +02:00
parent ea48fb3947
commit 1f66b2c980
16 changed files with 356 additions and 175 deletions

View File

@@ -16,8 +16,8 @@ namespace MareSynchronosServer.Hubs
private bool IsModerator => _dbContext.Users.Single(b => b.UID == AuthenticatedUserId).IsModerator || IsAdmin; private bool IsModerator => _dbContext.Users.Single(b => b.UID == AuthenticatedUserId).IsModerator || IsAdmin;
private List<string> OnlineAdmins => _dbContext.Users.Where(u => !string.IsNullOrEmpty(u.CharacterIdentification) && (u.IsModerator || u.IsAdmin)) private List<string> OnlineAdmins => _dbContext.Users.Where(u => (u.IsModerator || u.IsAdmin)).Select(u => u.UID).ToList();
.Select(u => u.UID).ToList();
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
[HubMethodName(Api.SendAdminChangeModeratorStatus)] [HubMethodName(Api.SendAdminChangeModeratorStatus)]
public async Task ChangeModeratorStatus(string uid, bool isModerator) public async Task ChangeModeratorStatus(string uid, bool isModerator)
@@ -101,13 +101,14 @@ namespace MareSynchronosServer.Hubs
{ {
if (!IsModerator) return null; 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, UID = b.UID,
IsModerator = b.IsModerator, IsModerator = b.IsModerator,
IsAdmin = b.IsAdmin IsAdmin = b.IsAdmin
}).ToListAsync().ConfigureAwait(false); }).ToList();
} }
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
@@ -134,11 +135,10 @@ namespace MareSynchronosServer.Hubs
await _dbContext.SaveChangesAsync().ConfigureAwait(false); await _dbContext.SaveChangesAsync().ConfigureAwait(false);
await Clients.Users(OnlineAdmins).SendAsync(Api.OnAdminUpdateOrAddBannedUser, dto).ConfigureAwait(false); await Clients.Users(OnlineAdmins).SendAsync(Api.OnAdminUpdateOrAddBannedUser, dto).ConfigureAwait(false);
var bannedUser = var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
await _dbContext.Users.SingleOrDefaultAsync(u => u.CharacterIdentification == dto.CharacterHash).ConfigureAwait(false); if (!string.IsNullOrEmpty(bannedUser))
if (bannedUser != null)
{ {
await Clients.User(bannedUser.UID).SendAsync(Api.OnAdminForcedReconnect).ConfigureAwait(false); await Clients.User(bannedUser).SendAsync(Api.OnAdminForcedReconnect).ConfigureAwait(false);
} }
} }

View File

@@ -76,7 +76,7 @@ namespace MareSynchronosServer.Hubs
IsForbidden = forbiddenFile != null, IsForbidden = forbiddenFile != null,
Hash = hash.Key, Hash = hash.Key,
Size = hash.Value, Size = hash.Value,
Url = new Uri(cdnFullUri, hash.Key.ToUpperInvariant()).ToString() Url = new Uri(_cdnFullUri, hash.Key.ToUpperInvariant()).ToString()
}); });
} }

View File

@@ -21,9 +21,9 @@ namespace MareSynchronosServer.Hubs
{ {
_logger.LogInformation("User {AuthenticatedUserId} deleted their account", AuthenticatedUserId); _logger.LogInformation("User {AuthenticatedUserId} deleted their account", AuthenticatedUserId);
string userid = AuthenticatedUserId; string userid = AuthenticatedUserId;
var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == userid).ConfigureAwait(false); 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 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 auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == userid).ConfigureAwait(false);
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == 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, OtherUID = userid,
IsRemoved = true IsRemoved = true
}, userEntry.CharacterIdentification).ConfigureAwait(false); }, charaIdent).ConfigureAwait(false);
} }
_mareMetrics.DecGauge(MetricsAPI.GaugePairs, ownPairData.Count + otherPairData.Count); _mareMetrics.DecGauge(MetricsAPI.GaugePairs, ownPairData.Count + otherPairData.Count);
@@ -77,14 +77,18 @@ namespace MareSynchronosServer.Hubs
.Include(u => u.User) .Include(u => u.User)
.Include(u => u.OtherUser) .Include(u => u.OtherUser)
.Where(w => w.User.UID == ownUser.UID && !w.IsPaused) .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); .Select(e => e.OtherUser).ToListAsync().ConfigureAwait(false);
var otherOnlineUsers =
otherUsers.Where(u => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(u.UID)));
var otherEntries = await _dbContext.ClientPairs.AsNoTracking() var otherEntries = await _dbContext.ClientPairs.AsNoTracking()
.Include(u => u.User) .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); await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync(Api.OnUserAddOnlinePairedPlayer, ownIdent).ConfigureAwait(false);
return otherEntries.Select(e => e.User.CharacterIdentification).Distinct().ToList(); return otherEntries.Select(e => _clientIdentService.GetCharacterIdentForUid(e.User.UID)).Distinct().ToList();
} }
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
@@ -152,12 +156,14 @@ namespace MareSynchronosServer.Hubs
userToOther.UserUID == user.UID userToOther.UserUID == user.UID
&& !userToOther.IsPaused && !userToOther.IsPaused
&& !otherToUser.IsPaused && !otherToUser.IsPaused
&& visibleCharacterIds.Contains(userToOther.OtherUser.CharacterIdentification)
select otherToUser.UserUID; select otherToUser.UserUID;
var otherEntries = await query.ToListAsync().ConfigureAwait(false); 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.CounterUserPushData);
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, otherEntries.Count); _mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, otherEntries.Count);
@@ -199,6 +205,7 @@ namespace MareSynchronosServer.Hubs
}, string.Empty).ConfigureAwait(false); }, string.Empty).ConfigureAwait(false);
if (otherEntry != null) if (otherEntry != null)
{ {
var userIdent = _clientIdentService.GetCharacterIdentForUid(user.UID);
await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs, await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs,
new ClientPairDto() new ClientPairDto()
{ {
@@ -207,14 +214,15 @@ namespace MareSynchronosServer.Hubs
IsPaused = otherEntry.IsPaused, IsPaused = otherEntry.IsPaused,
IsPausedFromOthers = false, IsPausedFromOthers = false,
IsSynced = true 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) await Clients.User(user.UID)
.SendAsync(Api.OnUserAddOnlinePairedPlayer, otherUser.CharacterIdentification).ConfigureAwait(false); .SendAsync(Api.OnUserAddOnlinePairedPlayer, otherIdent).ConfigureAwait(false);
await Clients.User(otherUser.UID) 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; pair.IsPaused = isPaused;
_dbContext.Update(pair); _dbContext.Update(pair);
await _dbContext.SaveChangesAsync().ConfigureAwait(false); await _dbContext.SaveChangesAsync().ConfigureAwait(false);
var selfCharaIdent = (await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false)).CharacterIdentification; var selfCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
var otherCharaIdent = (await _dbContext.Users.SingleAsync(u => u.UID == otherUserUid).ConfigureAwait(false)).CharacterIdentification; var otherCharaIdent = _clientIdentService.GetCharacterIdentForUid(pair.OtherUserUID);
var otherEntry = OppositeEntry(otherUserUid); var otherEntry = OppositeEntry(otherUserUid);
await Clients.User(AuthenticatedUserId) await Clients.User(AuthenticatedUserId)
@@ -282,27 +290,29 @@ namespace MareSynchronosServer.Hubs
_dbContext.ClientPairs.Remove(wl); _dbContext.ClientPairs.Remove(wl);
await _dbContext.SaveChangesAsync().ConfigureAwait(false); await _dbContext.SaveChangesAsync().ConfigureAwait(false);
var otherEntry = OppositeEntry(uid); var otherEntry = OppositeEntry(uid);
var otherIdent = _clientIdentService.GetCharacterIdentForUid(otherUser.UID);
await Clients.User(sender.UID) await Clients.User(sender.UID)
.SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto() .SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto()
{ {
OtherUID = otherUser.UID, OtherUID = otherUser.UID,
IsRemoved = true IsRemoved = true
}, otherUser.CharacterIdentification).ConfigureAwait(false); }, otherIdent).ConfigureAwait(false);
if (otherEntry != null) if (otherEntry != null)
{ {
if (!string.IsNullOrEmpty(otherUser.CharacterIdentification)) if (!string.IsNullOrEmpty(otherIdent))
{ {
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
await Clients.User(sender.UID) await Clients.User(sender.UID)
.SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherUser.CharacterIdentification).ConfigureAwait(false); .SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherIdent).ConfigureAwait(false);
await Clients.User(otherUser.UID) 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() await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto()
{ {
OtherUID = sender.UID, OtherUID = sender.UID,
IsPaused = otherEntry.IsPaused, IsPaused = otherEntry.IsPaused,
IsPausedFromOthers = false, IsPausedFromOthers = false,
IsSynced = false IsSynced = false
}, sender.CharacterIdentification).ConfigureAwait(false); }, ownIdent).ConfigureAwait(false);
} }
} }

View File

@@ -3,11 +3,13 @@ using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using MareSynchronos.API; using MareSynchronos.API;
using MareSynchronosServer.Services;
using MareSynchronosShared.Authentication; using MareSynchronosShared.Authentication;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models; using MareSynchronosShared.Models;
using MareSynchronosShared.Protos; using MareSynchronosShared.Protos;
using MareSynchronosShared.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
@@ -15,133 +17,134 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; 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<MareHub> _logger;
private readonly MareDbContext _dbContext;
private readonly Uri _cdnFullUri;
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient,
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor,
IClientIdentificationService clientIdentService)
{ {
private readonly MareMetrics _mareMetrics; _mareMetrics = mareMetrics;
private readonly AuthService.AuthServiceClient _authServiceClient; _authServiceClient = authServiceClient;
private readonly FileService.FileServiceClient _fileServiceClient; _fileServiceClient = fileServiceClient;
private readonly SystemInfoService _systemInfoService; _systemInfoService = systemInfoService;
private readonly IHttpContextAccessor contextAccessor; _cdnFullUri = new Uri(configuration.GetRequiredSection("MareSynchronos").GetValue<string>("CdnFullUrl"));
private readonly ILogger<MareHub> _logger; _contextAccessor = contextAccessor;
private readonly MareDbContext _dbContext; _clientIdentService = clientIdentService;
private readonly Uri cdnFullUri; _logger = logger;
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient, _dbContext = mareDbContext;
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor) }
[HubMethodName(Api.InvokeHeartbeat)]
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
public async Task<ConnectionDto> 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; var user = (await _dbContext.Users.SingleAsync(u => u.UID == userId).ConfigureAwait(false));
_authServiceClient = authServiceClient; var existingIdent = _clientIdentService.GetCharacterIdentForUid(userId);
_fileServiceClient = fileServiceClient; if (!string.IsNullOrEmpty(existingIdent) && characterIdentification != existingIdent)
_systemInfoService = systemInfoService;
cdnFullUri = new Uri(configuration.GetRequiredSection("MareSynchronos").GetValue<string>("CdnFullUrl"));
this.contextAccessor = contextAccessor;
_logger = logger;
_dbContext = mareDbContext;
}
[HubMethodName(Api.InvokeHeartbeat)]
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
public async Task<ConnectionDto> 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)); return new ConnectionDto()
if (!string.IsNullOrEmpty(user.CharacterIdentification) && characterIdentification != user.CharacterIdentification)
{ {
return new ConnectionDto() ServerVersion = Api.Version
{
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
}; };
} }
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()); ServerVersion = Api.Version
_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<User> GetAuthenticatedUserUntrackedAsync()
{
return await _dbContext.Users.AsNoTrackingWithIdentityResolution().SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
}
} }
}
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<User> GetAuthenticatedUserUntrackedAsync()
{
return await _dbContext.Users.AsNoTrackingWithIdentityResolution().SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
}
}

View File

@@ -29,11 +29,6 @@ namespace MareSynchronosServer
context.SaveChanges(); context.SaveChanges();
// clean up residuals // 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 looseFiles = context.Files.Where(f => f.Uploaded == false);
var unfinishedRegistrations = context.LodeStoneAuth.Where(c => c.StartedAt != null); var unfinishedRegistrations = context.LodeStoneAuth.Where(c => c.StartedAt != null);
context.RemoveRange(unfinishedRegistrations); context.RemoveRange(unfinishedRegistrations);

View File

@@ -1,32 +1,32 @@
using System; using System;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MareSynchronos.API; using MareSynchronos.API;
using MareSynchronosServer.Hubs; using MareSynchronosServer.Hubs;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models; using MareSynchronosShared.Services;
using MareSynchronosShared.Protos;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MareSynchronosServer; namespace MareSynchronosServer.Services;
public class SystemInfoService : IHostedService, IDisposable public class SystemInfoService : IHostedService, IDisposable
{ {
private readonly MareMetrics _mareMetrics; private readonly MareMetrics _mareMetrics;
private readonly IClientIdentificationService clientIdentService;
private readonly IServiceProvider _services; private readonly IServiceProvider _services;
private readonly ILogger<SystemInfoService> _logger; private readonly ILogger<SystemInfoService> _logger;
private readonly IHubContext<MareHub> _hubContext; private readonly IHubContext<MareHub> _hubContext;
private Timer _timer; private Timer _timer;
public SystemInfoDto SystemInfoDto { get; private set; } = new(); public SystemInfoDto SystemInfoDto { get; private set; } = new();
public SystemInfoService(MareMetrics mareMetrics, IServiceProvider services, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext) public SystemInfoService(MareMetrics mareMetrics, IClientIdentificationService clientIdentService, IServiceProvider services, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
{ {
_mareMetrics = mareMetrics; _mareMetrics = mareMetrics;
this.clientIdentService = clientIdentService;
_services = services; _services = services;
_logger = logger; _logger = logger;
_hubContext = hubContext; _hubContext = hubContext;
@@ -56,8 +56,6 @@ public class SystemInfoService : IHostedService, IDisposable
using var scope = _services.CreateScope(); using var scope = _services.CreateScope();
using var db = scope.ServiceProvider.GetService<MareDbContext>()!; using var db = scope.ServiceProvider.GetService<MareDbContext>()!;
var users = db.Users.Count(c => c.CharacterIdentification != null);
SystemInfoDto = new SystemInfoDto() SystemInfoDto = new SystemInfoDto()
{ {
CacheUsage = 0, CacheUsage = 0,
@@ -65,7 +63,7 @@ public class SystemInfoService : IHostedService, IDisposable
RAMUsage = 0, RAMUsage = 0,
NetworkIn = 0, NetworkIn = 0,
NetworkOut = 0, NetworkOut = 0,
OnlineUsers = users, OnlineUsers = clientIdentService.GetOnlineUsers(),
UploadedFiles = 0 UploadedFiles = 0
}; };

View File

@@ -19,6 +19,8 @@ using Grpc.Net.Client.Configuration;
using Prometheus; using Prometheus;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using System.Collections.Generic; using System.Collections.Generic;
using MareSynchronosServer.Services;
using MareSynchronosShared.Services;
namespace MareSynchronosServer namespace MareSynchronosServer
{ {
@@ -101,18 +103,18 @@ namespace MareSynchronosServer
options.EnableThreadSafetyChecks(false); options.EnableThreadSafetyChecks(false);
}, mareConfig.GetValue("DbContextPoolSize", 1024)); }, mareConfig.GetValue("DbContextPoolSize", 1024));
services.AddHostedService(provider => provider.GetService<SystemInfoService>()); services.AddHostedService(provider => provider.GetService<SystemInfoService>());
services.AddAuthentication(options => services.AddAuthentication(options =>
{ {
options.DefaultScheme = SecretKeyGrpcAuthenticationHandler.AuthScheme; options.DefaultScheme = SecretKeyGrpcAuthenticationHandler.AuthScheme;
}) }).AddScheme<AuthenticationSchemeOptions, SecretKeyGrpcAuthenticationHandler>(SecretKeyGrpcAuthenticationHandler.AuthScheme, _ => { });
.AddScheme<AuthenticationSchemeOptions, SecretKeyGrpcAuthenticationHandler>(SecretKeyGrpcAuthenticationHandler.AuthScheme, options => { });
services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>(); services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
var signalRserviceBuilder = services.AddSignalR(hubOptions => var signalRServiceBuilder = services.AddSignalR(hubOptions =>
{ {
hubOptions.MaximumReceiveMessageSize = long.MaxValue; hubOptions.MaximumReceiveMessageSize = long.MaxValue;
hubOptions.EnableDetailedErrors = true; hubOptions.EnableDetailedErrors = true;
@@ -120,13 +122,28 @@ namespace MareSynchronosServer
hubOptions.StreamBufferCapacity = 200; hubOptions.StreamBufferCapacity = 200;
hubOptions.AddFilter<SignalRLimitFilter>(); hubOptions.AddFilter<SignalRLimitFilter>();
}); });
// add redis related options
var redis = mareConfig.GetValue("RedisConnectionString", string.Empty); var redis = mareConfig.GetValue("RedisConnectionString", string.Empty);
if (!string.IsNullOrEmpty(redis)) if (!string.IsNullOrEmpty(redis))
{ {
signalRserviceBuilder.AddStackExchangeRedis(redis, options => signalRServiceBuilder.AddStackExchangeRedis(redis, options =>
{ {
options.Configuration.ChannelPrefix = "MareSynchronos"; options.Configuration.ChannelPrefix = "MareSynchronos";
}); });
services.AddStackExchangeRedisCache(opt =>
{
opt.Configuration = redis;
opt.InstanceName = "MareSynchronos";
});
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
services.AddHostedService(p => p.GetService<DistributedClientIdentificationService>());
}
else
{
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
services.AddHostedService(p => p.GetService<LocalClientIdentificationService>());
} }
} }

View File

@@ -14,6 +14,7 @@ using MareSynchronosServices.Authentication;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models; using MareSynchronosShared.Models;
using MareSynchronosShared.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -26,6 +27,7 @@ public class DiscordBot : IHostedService
{ {
private readonly CleanupService cleanupService; private readonly CleanupService cleanupService;
private readonly MareMetrics metrics; private readonly MareMetrics metrics;
private readonly IClientIdentificationService clientService;
private readonly IServiceProvider services; private readonly IServiceProvider services;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly ILogger<DiscordBot> logger; private readonly ILogger<DiscordBot> logger;
@@ -44,10 +46,11 @@ public class DiscordBot : IHostedService
private SemaphoreSlim semaphore; private SemaphoreSlim semaphore;
public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger) public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IClientIdentificationService clientService, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
{ {
this.cleanupService = cleanupService; this.cleanupService = cleanupService;
this.metrics = metrics; this.metrics = metrics;
this.clientService = clientService;
this.services = services; this.services = services;
_configuration = configuration.GetRequiredSection("MareSynchronos"); _configuration = configuration.GetRequiredSection("MareSynchronos");
this.logger = logger; this.logger = logger;
@@ -687,13 +690,7 @@ public class DiscordBot : IHostedService
updateStatusCts = new(); updateStatusCts = new();
while (!updateStatusCts.IsCancellationRequested) while (!updateStatusCts.IsCancellationRequested)
{ {
await using var scope = services.CreateAsyncScope(); await discordClient.SetActivityAsync(new Game("Mare for " + clientService.GetOnlineUsers() + " Users")).ConfigureAwait(false);
await using (var db = scope.ServiceProvider.GetRequiredService<MareDbContext>())
{
var users = db.Users.Count(c => c.CharacterIdentification != null);
await discordClient.SetActivityAsync(new Game("Mare for " + users + " Users")).ConfigureAwait(false);
}
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
} }
} }

View File

@@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Prometheus; using Prometheus;
using System.Collections.Generic; using System.Collections.Generic;
using MareSynchronosShared.Services;
namespace MareSynchronosServices; namespace MareSynchronosServices;
@@ -49,6 +50,24 @@ public class Startup
services.AddHostedService(provider => provider.GetService<CleanupService>()); services.AddHostedService(provider => provider.GetService<CleanupService>());
services.AddHostedService<DiscordBot>(); services.AddHostedService<DiscordBot>();
services.AddGrpc(); 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<IClientIdentificationService, DistributedClientIdentificationService>();
services.AddHostedService(p => p.GetService<DistributedClientIdentificationService>());
}
else
{
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
services.AddHostedService(p => p.GetService<LocalClientIdentificationService>());
}
} }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

View File

@@ -45,7 +45,7 @@ public class MareDbContext : DbContext
{ {
modelBuilder.Entity<Auth>().ToTable("auth"); modelBuilder.Entity<Auth>().ToTable("auth");
modelBuilder.Entity<User>().ToTable("users"); modelBuilder.Entity<User>().ToTable("users");
modelBuilder.Entity<User>().HasIndex(c => c.CharacterIdentification); //modelBuilder.Entity<User>().HasIndex(c => c.CharacterIdentification);
modelBuilder.Entity<FileCache>().ToTable("file_caches"); modelBuilder.Entity<FileCache>().ToTable("file_caches");
modelBuilder.Entity<FileCache>().HasIndex(c => c.UploaderUID); modelBuilder.Entity<FileCache>().HasIndex(c => c.UploaderUID);
modelBuilder.Entity<ClientPair>().ToTable("client_pairs"); modelBuilder.Entity<ClientPair>().ToTable("client_pairs");

View File

@@ -30,6 +30,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="6.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
<PackageReference Include="prometheus-net" Version="6.0.0" /> <PackageReference Include="prometheus-net" Version="6.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -7,8 +7,8 @@ namespace MareSynchronosShared.Models
[Key] [Key]
[MaxLength(10)] [MaxLength(10)]
public string UID { get; set; } public string UID { get; set; }
[MaxLength(100)] //[MaxLength(100)]
public string CharacterIdentification { get; set; } //public string CharacterIdentification { get; set; }
[Timestamp] [Timestamp]
public byte[] Timestamp { get; set; } public byte[] Timestamp { get; set; }

View File

@@ -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<string, string> 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<string, string>()) ? 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;
}
}

View File

@@ -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<string>("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);
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,10 @@
using MareSynchronosShared.Metrics;
namespace MareSynchronosShared.Services;
public class LocalClientIdentificationService : BaseClientIdentificationService
{
public LocalClientIdentificationService(MareMetrics metrics) : base(metrics)
{
}
}