rework server responsibilities (#18)
* rework server responsibilities add remote configuration * start metrics only when compiled as not debug * add some more logging to discord bot * fixes of some casts * make metrics port configurable, minor fixes * add docker bullshit * md formatting * adjustments to docker stuff * fix docker json files, fix some stuff in discord bot, add /useradd for Discord bot * adjust docker configs and fix sharded.bat * fixes for logs, cache file provider repeat trying to open filestream Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
@@ -1,7 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosShared.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
@@ -11,6 +8,7 @@ namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
// TODO: remove all of this and migrate it to the discord bot eventually
|
||||
private List<string> OnlineAdmins => _dbContext.Users.Where(u => (u.IsModerator || u.IsAdmin)).Select(u => u.UID).ToList();
|
||||
|
||||
[Authorize(Policy = "Admin")]
|
||||
@@ -116,11 +114,11 @@ public partial class MareHub
|
||||
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
await Clients.Users(OnlineAdmins).Client_AdminUpdateOrAddBannedUser(dto).ConfigureAwait(false);
|
||||
var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
|
||||
if (!string.IsNullOrEmpty(bannedUser))
|
||||
{
|
||||
await Clients.User(bannedUser).Client_AdminForcedReconnect().ConfigureAwait(false);
|
||||
}
|
||||
//var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
|
||||
//if (!string.IsNullOrEmpty(bannedUser))
|
||||
//{
|
||||
// await Clients.User(bannedUser).Client_AdminForcedReconnect().ConfigureAwait(false);
|
||||
//}
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Admin")]
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using MareSynchronos.API;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace MareSynchronosServer.Hubs
|
||||
{
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Protobuf;
|
||||
using Grpc.Core;
|
||||
using MareSynchronos.API;
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using MareSynchronosShared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using MareSynchronosServer.Utils;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ using MareSynchronosShared.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Metrics;
|
||||
@@ -72,7 +68,7 @@ public partial class MareHub
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
|
||||
var usersToSendOnlineTo = await SendOnlineToAllPairedUsers(ownIdent).ConfigureAwait(false);
|
||||
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(System.StringComparer.Ordinal).ToList();
|
||||
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
@@ -152,8 +148,8 @@ public partial class MareHub
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
|
||||
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), System.StringComparer.Ordinal)
|
||||
.Where(f => visibleCharacterIds.Contains(f.Value, System.StringComparer.Ordinal));
|
||||
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), StringComparer.Ordinal)
|
||||
.Where(f => visibleCharacterIds.Contains(f.Value, StringComparer.Ordinal));
|
||||
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
|
||||
@@ -172,7 +168,7 @@ public partial class MareHub
|
||||
|
||||
// don't allow adding yourself or nothing
|
||||
uid = uid.Trim();
|
||||
if (string.Equals(uid, AuthenticatedUserId, System.StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
||||
if (string.Equals(uid, AuthenticatedUserId, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
||||
|
||||
// grab other user, check if it exists and if a pair already exists
|
||||
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
|
||||
@@ -231,7 +227,7 @@ public partial class MareHub
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
// if the other user has paused the main user and there was no previous group connection don't send anything
|
||||
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, System.StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
|
||||
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
|
||||
{
|
||||
await Clients.User(user.UID).Client_UserChangePairedPlayer(otherIdent, true).ConfigureAwait(false);
|
||||
await Clients.User(otherUser.UID).Client_UserChangePairedPlayer(userIdent, true).ConfigureAwait(false);
|
||||
@@ -243,7 +239,7 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid, isPaused));
|
||||
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
if (pair == null) return;
|
||||
|
||||
@@ -288,7 +284,7 @@ public partial class MareHub
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid));
|
||||
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
|
||||
// check if client pair even exists
|
||||
ClientPair callerPair =
|
||||
@@ -331,7 +327,7 @@ public partial class MareHub
|
||||
if (!callerHadPaused && otherHadPaused) return;
|
||||
|
||||
var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, otherUserUid, System.StringComparison.Ordinal));
|
||||
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, otherUserUid, StringComparison.Ordinal));
|
||||
var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection;
|
||||
|
||||
// if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Claims;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using MareSynchronosShared.Services;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
@@ -23,7 +20,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
private readonly FileService.FileServiceClient _fileServiceClient;
|
||||
private readonly SystemInfoService _systemInfoService;
|
||||
private readonly IHttpContextAccessor _contextAccessor;
|
||||
private readonly GrpcClientIdentificationService _clientIdentService;
|
||||
private readonly IClientIdentificationService _clientIdentService;
|
||||
private readonly MareHubLogger _logger;
|
||||
private readonly MareDbContext _dbContext;
|
||||
private readonly Uri _cdnFullUri;
|
||||
@@ -33,18 +30,18 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
private readonly int _maxGroupUserCount;
|
||||
|
||||
public MareHub(MareMetrics mareMetrics, FileService.FileServiceClient fileServiceClient,
|
||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IOptions<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
|
||||
GrpcClientIdentificationService clientIdentService)
|
||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService,
|
||||
IConfigurationService<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
|
||||
IClientIdentificationService clientIdentService)
|
||||
{
|
||||
_mareMetrics = mareMetrics;
|
||||
_fileServiceClient = fileServiceClient;
|
||||
_systemInfoService = systemInfoService;
|
||||
var config = configuration.Value;
|
||||
_cdnFullUri = config.CdnFullUrl;
|
||||
_shardName = config.ShardName;
|
||||
_maxExistingGroupsByUser = config.MaxExistingGroupsByUser;
|
||||
_maxJoinedGroupsByUser = config.MaxJoinedGroupsByUser;
|
||||
_maxGroupUserCount = config.MaxGroupUserCount;
|
||||
_cdnFullUri = configuration.GetValue<Uri>(nameof(ServerConfiguration.CdnFullUrl));
|
||||
_shardName = configuration.GetValue<string>(nameof(ServerConfiguration.ShardName));
|
||||
_maxExistingGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxExistingGroupsByUser), 3);
|
||||
_maxJoinedGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxJoinedGroupsByUser), 6);
|
||||
_maxGroupUserCount = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxGroupUserCount), 100);
|
||||
_contextAccessor = contextAccessor;
|
||||
_clientIdentService = clientIdentService;
|
||||
_logger = new MareHubLogger(this, logger);
|
||||
@@ -109,14 +106,14 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Authenticated")]
|
||||
public async Task<bool> CheckClientHealth()
|
||||
public Task<bool> CheckClientHealth()
|
||||
{
|
||||
var needsReconnect = !_clientIdentService.IsOnCurrentServer(AuthenticatedUserId);
|
||||
if (needsReconnect)
|
||||
{
|
||||
_logger.LogCallWarning(MareHubLogger.Args(needsReconnect));
|
||||
}
|
||||
return needsReconnect;
|
||||
return Task.FromResult(needsReconnect);
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Security.Claims;
|
||||
using AspNetCoreRateLimit;
|
||||
using MareSynchronosShared;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
public class SignalRLimitFilter : IHubFilter
|
||||
{
|
||||
private readonly IRateLimitProcessor _processor;
|
||||
private readonly IHttpContextAccessor accessor;
|
||||
private readonly ILogger<SignalRLimitFilter> logger;
|
||||
private static readonly SemaphoreSlim ConnectionLimiterSemaphore = new(10);
|
||||
private static readonly SemaphoreSlim DisconnectLimiterSemaphore = new(10);
|
||||
|
||||
public SignalRLimitFilter(
|
||||
IOptions<IpRateLimitOptions> options, IProcessingStrategy processing, IIpPolicyStore policyStore, IHttpContextAccessor accessor, ILogger<SignalRLimitFilter> logger)
|
||||
{
|
||||
_processor = new IpRateLimitProcessor(options?.Value, policyStore, processing);
|
||||
this.accessor = accessor;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async ValueTask<object> InvokeMethodAsync(
|
||||
HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
|
||||
{
|
||||
var ip = accessor.GetIpAddress();
|
||||
var client = new ClientRequestIdentity
|
||||
{
|
||||
ClientIp = ip,
|
||||
Path = invocationContext.HubMethodName,
|
||||
HttpVerb = "ws",
|
||||
ClientId = invocationContext.Context.UserIdentifier
|
||||
};
|
||||
foreach (var rule in await _processor.GetMatchingRulesAsync(client).ConfigureAwait(false))
|
||||
{
|
||||
var counter = await _processor.ProcessRequestAsync(client, rule).ConfigureAwait(false);
|
||||
if (counter.Count > rule.Limit)
|
||||
{
|
||||
var authUserId = invocationContext.Context.User.Claims?.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||
var retry = counter.Timestamp.RetryAfterFrom(rule);
|
||||
logger.LogWarning("Method rate limit triggered from {ip}/{authUserId}: {method}", ip, authUserId, invocationContext.HubMethodName);
|
||||
throw new HubException($"call limit {retry}");
|
||||
}
|
||||
}
|
||||
|
||||
return await next(invocationContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Optional method
|
||||
public async Task OnConnectedAsync(HubLifetimeContext context, Func<HubLifetimeContext, Task> next)
|
||||
{
|
||||
await ConnectionLimiterSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var ip = accessor.GetIpAddress();
|
||||
var client = new ClientRequestIdentity
|
||||
{
|
||||
ClientIp = ip,
|
||||
Path = "Connect",
|
||||
HttpVerb = "ws",
|
||||
};
|
||||
foreach (var rule in await _processor.GetMatchingRulesAsync(client).ConfigureAwait(false))
|
||||
{
|
||||
var counter = await _processor.ProcessRequestAsync(client, rule).ConfigureAwait(false);
|
||||
if (counter.Count > rule.Limit)
|
||||
{
|
||||
var retry = counter.Timestamp.RetryAfterFrom(rule);
|
||||
logger.LogWarning("Connection rate limit triggered from {ip}", ip);
|
||||
ConnectionLimiterSemaphore.Release();
|
||||
throw new HubException($"Connection rate limit {retry}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await Task.Delay(25).ConfigureAwait(false);
|
||||
await next(context).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Error on OnConnectedAsync");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ConnectionLimiterSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnDisconnectedAsync(
|
||||
HubLifetimeContext context, Exception exception, Func<HubLifetimeContext, Exception, Task> next)
|
||||
{
|
||||
await DisconnectLimiterSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
if (exception != null)
|
||||
{
|
||||
logger.LogWarning(exception, "InitialException on OnDisconnectedAsync");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await next(context, exception).ConfigureAwait(false);
|
||||
await Task.Delay(25).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogWarning(e, "ThrownException on OnDisconnectedAsync");
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisconnectLimiterSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user