Switch Authentication to asynchronous streaming calls (#16)
* add base grpc service and swap auth service to streaming * remove Authorize from hub itself * remove unused usings * heave files server to net 7, add exception handling in grpc auth stream Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
@@ -4,12 +4,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using System.Globalization;
|
|
||||||
using MareSynchronos.API;
|
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Hubs;
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using MareSynchronosShared.Authentication;
|
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
@@ -38,7 +37,7 @@ public partial class MareHub
|
|||||||
await Task.Delay(1000).ConfigureAwait(false);
|
await Task.Delay(1000).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _authServiceClient.RemoveAuthAsync(new RemoveAuthRequest() { Uid = userid }).ConfigureAwait(false);
|
await _authServiceClient.RemoveAuthAsync(new UidMessage() { Uid = userid }).ConfigureAwait(false);
|
||||||
|
|
||||||
_dbContext.RemoveRange(ownPairData);
|
_dbContext.RemoveRange(ownPairData);
|
||||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
|||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using MareSynchronosServer.Services;
|
using MareSynchronosServer.Services;
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using MareSynchronosShared.Authentication;
|
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
@@ -18,7 +17,6 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace MareSynchronosServer.Hubs;
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public partial class MareHub : Hub<IMareHub>, IMareHub
|
public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||||
{
|
{
|
||||||
private readonly MareMetrics _mareMetrics;
|
private readonly MareMetrics _mareMetrics;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MareSynchronosServer.Services;
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
public class GrpcClientIdentificationService : IHostedService
|
public class GrpcClientIdentificationService : GrpcBaseService
|
||||||
{
|
{
|
||||||
private readonly string _shardName;
|
private readonly string _shardName;
|
||||||
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
||||||
@@ -22,14 +22,11 @@ public class GrpcClientIdentificationService : IHostedService
|
|||||||
private readonly MareMetrics _metrics;
|
private readonly MareMetrics _metrics;
|
||||||
protected readonly ConcurrentDictionary<string, UidWithIdent> OnlineClients = new(StringComparer.Ordinal);
|
protected readonly ConcurrentDictionary<string, UidWithIdent> OnlineClients = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, UidWithIdent> RemoteCachedIdents = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, UidWithIdent> RemoteCachedIdents = new(StringComparer.Ordinal);
|
||||||
private bool _grpcIsFaulty = false;
|
|
||||||
private ConcurrentQueue<IdentChange> _identChangeQueue = new();
|
private ConcurrentQueue<IdentChange> _identChangeQueue = new();
|
||||||
private CancellationTokenSource _streamCts = new();
|
|
||||||
private CancellationTokenSource _faultCheckCts = new();
|
|
||||||
|
|
||||||
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient,
|
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient,
|
||||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamOut,
|
IdentificationService.IdentificationServiceClient gprcIdentClientStreamOut,
|
||||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IConfiguration configuration)
|
IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IConfiguration configuration) : base(logger)
|
||||||
{
|
{
|
||||||
var config = configuration.GetSection("MareSynchronos");
|
var config = configuration.GetSection("MareSynchronos");
|
||||||
_shardName = config.GetValue("ShardName", "Main");
|
_shardName = config.GetValue("ShardName", "Main");
|
||||||
@@ -135,62 +132,6 @@ public class GrpcClientIdentificationService : IHostedService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_ = RestartStreams();
|
|
||||||
_ = CheckGrpcFaults(_faultCheckCts.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_faultCheckCts.Cancel();
|
|
||||||
_streamCts.Cancel();
|
|
||||||
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckGrpcFaults(CancellationToken ct)
|
|
||||||
{
|
|
||||||
while (!ct.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch { SetGrpcFaulty(); }
|
|
||||||
await Task.Delay(250).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RestartStreams()
|
|
||||||
{
|
|
||||||
_streamCts?.Cancel();
|
|
||||||
_streamCts?.Dispose();
|
|
||||||
_streamCts = new();
|
|
||||||
if (!_grpcIsFaulty)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false);
|
|
||||||
|
|
||||||
RemoteCachedIdents.Clear();
|
|
||||||
_ = StreamOnlineClientData(_streamCts.Token);
|
|
||||||
_ = ReceiveOnlineClientData(_streamCts.Token);
|
|
||||||
var remoteOnlineClients = await _grpcIdentClient.GetAllIdentsAsync(new ServerMessage()
|
|
||||||
{
|
|
||||||
ServerId = _shardName
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
foreach (var result in remoteOnlineClients.UidWithIdent)
|
|
||||||
{
|
|
||||||
RemoteCachedIdents[result.Uid.Uid] = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
SetGrpcFaulty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StreamOnlineClientData(CancellationToken cts)
|
private async Task StreamOnlineClientData(CancellationToken cts)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -254,59 +195,49 @@ public class GrpcClientIdentificationService : IHostedService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
protected override Task StartAsyncInternal(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
return Task.CompletedTask;
|
||||||
{
|
|
||||||
var result = await toExecute.ConfigureAwait(false);
|
|
||||||
|
|
||||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
SetGrpcFaulty();
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
protected override async Task StopAsyncInternal(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
||||||
{
|
|
||||||
await toExecute.ConfigureAwait(false);
|
|
||||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
SetGrpcFaulty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckFaultStateAndResend()
|
protected override async Task OnGrpcRestore()
|
||||||
{
|
{
|
||||||
if (_grpcIsFaulty)
|
var msg = new ServerIdentMessage();
|
||||||
|
msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage()
|
||||||
{
|
{
|
||||||
await RestartStreams().ConfigureAwait(false);
|
UidWithIdent = c.Value
|
||||||
var msg = new ServerIdentMessage();
|
}));
|
||||||
msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage()
|
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false);
|
||||||
{
|
|
||||||
UidWithIdent = c.Value
|
|
||||||
}));
|
|
||||||
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false);
|
|
||||||
_logger.LogInformation("GRPC connection is restored");
|
|
||||||
_grpcIsFaulty = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetGrpcFaulty()
|
protected override async Task PreStartStream()
|
||||||
{
|
{
|
||||||
if (!_grpcIsFaulty)
|
await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false);
|
||||||
|
|
||||||
|
RemoteCachedIdents.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task StartStream(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_ = StreamOnlineClientData(ct);
|
||||||
|
_ = ReceiveOnlineClientData(ct);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task PostStartStream()
|
||||||
|
{
|
||||||
|
var remoteOnlineClients = await _grpcIdentClient.GetAllIdentsAsync(new ServerMessage()
|
||||||
{
|
{
|
||||||
_grpcIsFaulty = true;
|
ServerId = _shardName
|
||||||
_logger.LogWarning("GRPC connection is faulty");
|
}).ConfigureAwait(false);
|
||||||
|
foreach (var result in remoteOnlineClients.UidWithIdent)
|
||||||
|
{
|
||||||
|
RemoteCachedIdents[result.Uid.Uid] = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using MareSynchronosServer.Services;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using MareSynchronosServer.RequirementHandlers;
|
using MareSynchronosServer.RequirementHandlers;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
|
||||||
namespace MareSynchronosServer;
|
namespace MareSynchronosServer;
|
||||||
|
|
||||||
@@ -119,8 +120,10 @@ public class Startup
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<GrpcAuthenticationService>();
|
||||||
services.AddSingleton<GrpcClientIdentificationService>();
|
services.AddSingleton<GrpcClientIdentificationService>();
|
||||||
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
|
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
|
||||||
|
services.AddHostedService(p => p.GetService<GrpcAuthenticationService>());
|
||||||
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
|
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
|
||||||
|
|
||||||
services.AddDbContextPool<MareDbContext>(options =>
|
services.AddDbContextPool<MareDbContext>(options =>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MareSynchronosServices.Authentication;
|
namespace MareSynchronosServices.Authentication;
|
||||||
|
|
||||||
public class FailedAuthorization : IDisposable
|
internal class FailedAuthorization : IDisposable
|
||||||
{
|
{
|
||||||
private int failedAttempts = 1;
|
private int failedAttempts = 1;
|
||||||
public int FailedAttempts => failedAttempts;
|
public int FailedAttempts => failedAttempts;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace MareSynchronosServices.Authentication;
|
namespace MareSynchronosServices.Authentication;
|
||||||
|
|
||||||
public class SecretKeyAuthenticationHandler
|
internal class SecretKeyAuthenticationHandler
|
||||||
{
|
{
|
||||||
private readonly ILogger<SecretKeyAuthenticationHandler> logger;
|
private readonly ILogger<SecretKeyAuthenticationHandler> logger;
|
||||||
private readonly MareMetrics metrics;
|
private readonly MareMetrics metrics;
|
||||||
@@ -60,7 +60,7 @@ public class SecretKeyAuthenticationHandler
|
|||||||
if (string.IsNullOrEmpty(secretKey))
|
if (string.IsNullOrEmpty(secretKey))
|
||||||
{
|
{
|
||||||
metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||||
return new AuthReply() { Success = false, Uid = string.Empty };
|
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (failedAuthLock)
|
lock (failedAuthLock)
|
||||||
@@ -86,7 +86,7 @@ public class SecretKeyAuthenticationHandler
|
|||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
logger.LogWarning("TempBan {ip} for authorization spam", ip);
|
logger.LogWarning("TempBan {ip} for authorization spam", ip);
|
||||||
return new AuthReply() { Success = false, Uid = string.Empty };
|
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ public class SecretKeyAuthenticationHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AuthReply() { Success = false, Uid = string.Empty };
|
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
|
metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
|
||||||
@@ -152,7 +152,7 @@ public class SecretKeyAuthenticationHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||||
return new AuthReply() { Success = false, Uid = string.Empty };
|
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (authDictLock)
|
lock (authDictLock)
|
||||||
@@ -163,7 +163,7 @@ public class SecretKeyAuthenticationHandler
|
|||||||
|
|
||||||
metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
|
metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
|
||||||
|
|
||||||
return new AuthReply() { Success = true, Uid = uid };
|
return new AuthReply() { Success = true, Uid = new UidMessage() { Uid = uid } };
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKeyAuthenticationHandler(IConfiguration configuration, ILogger<SecretKeyAuthenticationHandler> logger, MareMetrics metrics)
|
public SecretKeyAuthenticationHandler(IConfiguration configuration, ILogger<SecretKeyAuthenticationHandler> logger, MareMetrics metrics)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MareSynchronosServices;
|
namespace MareSynchronosServices;
|
||||||
|
|
||||||
public class CleanupService : IHostedService, IDisposable
|
internal class CleanupService : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly MareMetrics metrics;
|
private readonly MareMetrics metrics;
|
||||||
private readonly SecretKeyAuthenticationHandler _authService;
|
private readonly SecretKeyAuthenticationHandler _authService;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -101,10 +100,10 @@ internal class IdentityHandler
|
|||||||
{
|
{
|
||||||
identChanges[serverId] = new ConcurrentQueue<IdentChange>();
|
identChanges[serverId] = new ConcurrentQueue<IdentChange>();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal record ServerIdentity
|
internal record ServerIdentity
|
||||||
{
|
{
|
||||||
public string ServerId { get; set; } = string.Empty;
|
public string ServerId { get; set; } = string.Empty;
|
||||||
public string CharacterIdent { get; set; } = string.Empty;
|
public string CharacterIdent { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MareSynchronosServices.Services;
|
namespace MareSynchronosServices.Services;
|
||||||
|
|
||||||
public class AuthenticationService : AuthService.AuthServiceBase
|
internal class AuthenticationService : AuthService.AuthServiceBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<AuthenticationService> _logger;
|
private readonly ILogger<AuthenticationService> _logger;
|
||||||
private readonly MareDbContext _dbContext;
|
private readonly MareDbContext _dbContext;
|
||||||
@@ -20,12 +20,16 @@ public class AuthenticationService : AuthService.AuthServiceBase
|
|||||||
_authHandler = authHandler;
|
_authHandler = authHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<AuthReply> Authorize(AuthRequest request, ServerCallContext context)
|
public override async Task Authorize(IAsyncStreamReader<AuthRequest> requestStream, IServerStreamWriter<AuthReply> responseStream, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return await _authHandler.AuthenticateAsync(_dbContext, request.Ip, request.SecretKey);
|
await foreach (var input in requestStream.ReadAllAsync(context.CancellationToken).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var response = await _authHandler.AuthenticateAsync(_dbContext, input.Ip, input.SecretKey).ConfigureAwait(false);
|
||||||
|
await responseStream.WriteAsync(response, context.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<Empty> RemoveAuth(RemoveAuthRequest request, ServerCallContext context)
|
public override Task<Empty> RemoveAuth(UidMessage request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Removing Authentication for {uid}", request.Uid);
|
_logger.LogInformation("Removing Authentication for {uid}", request.Uid);
|
||||||
_authHandler.RemoveAuthentication(request.Uid);
|
_authHandler.RemoveAuthentication(request.Uid);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using MareSynchronosServer;
|
using MareSynchronosServer;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -14,13 +14,13 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
|
|||||||
{
|
{
|
||||||
public const string AuthScheme = "SecretKeyGrpcAuth";
|
public const string AuthScheme = "SecretKeyGrpcAuth";
|
||||||
|
|
||||||
private readonly AuthService.AuthServiceClient _authClient;
|
private readonly GrpcAuthenticationService _grpcAuthService;
|
||||||
private readonly IHttpContextAccessor _accessor;
|
private readonly IHttpContextAccessor _accessor;
|
||||||
|
|
||||||
public SecretKeyGrpcAuthenticationHandler(IHttpContextAccessor accessor, AuthService.AuthServiceClient authClient,
|
public SecretKeyGrpcAuthenticationHandler(IHttpContextAccessor accessor, GrpcAuthenticationService authClient,
|
||||||
IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
|
IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
|
||||||
{
|
{
|
||||||
this._authClient = authClient;
|
this._grpcAuthService = authClient;
|
||||||
_accessor = accessor;
|
_accessor = accessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
|
|||||||
|
|
||||||
var ip = _accessor.GetIpAddress();
|
var ip = _accessor.GetIpAddress();
|
||||||
|
|
||||||
var authResult = await _authClient.AuthorizeAsync(new AuthRequest() { Ip = ip, SecretKey = authHeader }).ConfigureAwait(false);
|
var authResult = await _grpcAuthService.AuthorizeAsync(ip, authHeader).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!authResult.Success)
|
if (!authResult.Success)
|
||||||
{
|
{
|
||||||
@@ -44,7 +44,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
|
|||||||
|
|
||||||
var claims = new List<Claim>
|
var claims = new List<Claim>
|
||||||
{
|
{
|
||||||
new(ClaimTypes.NameIdentifier, uid),
|
new(ClaimTypes.NameIdentifier, uid.Uid),
|
||||||
new(ClaimTypes.Authentication, authHeader)
|
new(ClaimTypes.Authentication, authHeader)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace MareSynchronosShared.Data;
|
namespace MareSynchronosShared.Data;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
using System;
|
namespace MareSynchronosShared.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Models;
|
|
||||||
|
|
||||||
public class GroupBan
|
public class GroupBan
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ option csharp_namespace = "MareSynchronosShared.Protos";
|
|||||||
package mareservices;
|
package mareservices;
|
||||||
|
|
||||||
service AuthService {
|
service AuthService {
|
||||||
rpc Authorize (AuthRequest) returns (AuthReply);
|
rpc Authorize (stream AuthRequest) returns (stream AuthReply);
|
||||||
rpc RemoveAuth (RemoveAuthRequest) returns (Empty);
|
rpc RemoveAuth (UidMessage) returns (Empty);
|
||||||
rpc ClearUnauthorized (Empty) returns (Empty);
|
rpc ClearUnauthorized (Empty) returns (Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,10 +101,6 @@ message FileSizeResponse {
|
|||||||
map<string, int64> hashToFileSize = 1;
|
map<string, int64> hashToFileSize = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RemoveAuthRequest {
|
|
||||||
string uid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthRequest {
|
message AuthRequest {
|
||||||
string ip = 1;
|
string ip = 1;
|
||||||
string secretKey = 2;
|
string secretKey = 2;
|
||||||
@@ -112,5 +108,5 @@ message AuthRequest {
|
|||||||
|
|
||||||
message AuthReply {
|
message AuthReply {
|
||||||
bool success = 1;
|
bool success = 1;
|
||||||
string uid = 2;
|
UidMessage uid = 2;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
|
public class GrpcAuthenticationService : GrpcBaseService
|
||||||
|
{
|
||||||
|
private record AuthRequestInternal
|
||||||
|
{
|
||||||
|
public AuthRequest Request { get; set; }
|
||||||
|
public long Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly AuthService.AuthServiceClient _authClient;
|
||||||
|
private readonly ConcurrentQueue<AuthRequestInternal> _requestQueue = new();
|
||||||
|
private readonly ConcurrentDictionary<long, AuthReply> _authReplies = new();
|
||||||
|
private long _requestId = 0;
|
||||||
|
|
||||||
|
public GrpcAuthenticationService(ILogger<GrpcAuthenticationService> logger, AuthService.AuthServiceClient authClient) : base(logger)
|
||||||
|
{
|
||||||
|
_authClient = authClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthReply> AuthorizeAsync(string ip, string secretKey)
|
||||||
|
{
|
||||||
|
using var sha1 = SHA1.Create();
|
||||||
|
var id = Interlocked.Increment(ref _requestId);
|
||||||
|
_requestQueue.Enqueue(new AuthRequestInternal()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Request = new AuthRequest()
|
||||||
|
{
|
||||||
|
Ip = ip,
|
||||||
|
SecretKey = secretKey,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
|
||||||
|
AuthReply response = null;
|
||||||
|
|
||||||
|
while (!GrpcIsFaulty && !cts.IsCancellationRequested && !_authReplies.TryRemove(id, out response))
|
||||||
|
{
|
||||||
|
await Task.Delay(10, cts.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response ?? new AuthReply
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GrpcAuthStream(CancellationToken token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = _authClient.Authorize(cancellationToken: token);
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
while (_requestQueue.TryDequeue(out var request))
|
||||||
|
{
|
||||||
|
await stream.RequestStream.WriteAsync(request.Request, token).ConfigureAwait(false);
|
||||||
|
await stream.ResponseStream.MoveNext(token).ConfigureAwait(false);
|
||||||
|
_authReplies[request.Id] = stream.ResponseStream.Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(10, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SetGrpcFaulty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnGrpcRestore()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task PostStartStream()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task PreStartStream()
|
||||||
|
{
|
||||||
|
_requestQueue.Clear();
|
||||||
|
_authReplies.Clear();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task StartAsyncInternal(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task StartStream(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_ = GrpcAuthStream(ct);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task StopAsyncInternal(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
|
public abstract class GrpcBaseService : IHostedService, IDisposable
|
||||||
|
{
|
||||||
|
protected GrpcBaseService(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource _faultCheckCts = new();
|
||||||
|
private CancellationTokenSource _streamCts = new();
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
protected bool GrpcIsFaulty { get; private set; }
|
||||||
|
|
||||||
|
protected abstract Task StartAsyncInternal(CancellationToken cancellationToken);
|
||||||
|
protected abstract Task StopAsyncInternal(CancellationToken cancellationToken);
|
||||||
|
protected abstract Task OnGrpcRestore();
|
||||||
|
protected abstract Task PreStartStream();
|
||||||
|
protected abstract Task StartStream(CancellationToken ct);
|
||||||
|
protected abstract Task PostStartStream();
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_ = RestartStreams();
|
||||||
|
_ = CheckGrpcFaults(_faultCheckCts.Token);
|
||||||
|
await StartAsyncInternal(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_faultCheckCts.Cancel();
|
||||||
|
_streamCts.Cancel();
|
||||||
|
await StopAsyncInternal(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RestartStreams()
|
||||||
|
{
|
||||||
|
_streamCts?.Cancel();
|
||||||
|
_streamCts?.Dispose();
|
||||||
|
_streamCts = new();
|
||||||
|
if (!GrpcIsFaulty)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await PreStartStream().ConfigureAwait(false);
|
||||||
|
await StartStream(_streamCts.Token).ConfigureAwait(false);
|
||||||
|
await PostStartStream().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SetGrpcFaulty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetGrpcFaulty()
|
||||||
|
{
|
||||||
|
if (!GrpcIsFaulty)
|
||||||
|
{
|
||||||
|
GrpcIsFaulty = true;
|
||||||
|
_logger.LogWarning("GRPC connection is faulty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckGrpcFaults(CancellationToken ct)
|
||||||
|
{
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch { SetGrpcFaulty(); }
|
||||||
|
await Task.Delay(250).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckFaultStateAndResend()
|
||||||
|
{
|
||||||
|
if (GrpcIsFaulty)
|
||||||
|
{
|
||||||
|
await RestartStreams().ConfigureAwait(false);
|
||||||
|
await OnGrpcRestore().ConfigureAwait(false);
|
||||||
|
_logger.LogInformation("GRPC connection is restored");
|
||||||
|
GrpcIsFaulty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await toExecute.ConfigureAwait(false);
|
||||||
|
|
||||||
|
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SetGrpcFaulty();
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await toExecute.ConfigureAwait(false);
|
||||||
|
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SetGrpcFaulty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_streamCts?.Dispose();
|
||||||
|
_faultCheckCts?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Utils;
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using MareSynchronosShared.Authentication;
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@@ -47,6 +48,7 @@ public class Startup
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.AddSingleton<GrpcAuthenticationService>();
|
||||||
services.AddSingleton(new MareMetrics(new List<string> {
|
services.AddSingleton(new MareMetrics(new List<string> {
|
||||||
}, new List<string>
|
}, new List<string>
|
||||||
{
|
{
|
||||||
@@ -72,6 +74,7 @@ public class Startup
|
|||||||
}, mareSettings.GetValue("DbContextPoolSize", 1024));
|
}, mareSettings.GetValue("DbContextPoolSize", 1024));
|
||||||
|
|
||||||
services.AddHostedService<CleanupService>();
|
services.AddHostedService<CleanupService>();
|
||||||
|
services.AddHostedService(p => p.GetService<GrpcAuthenticationService>());
|
||||||
|
|
||||||
services.AddAuthentication(options =>
|
services.AddAuthentication(options =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user