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.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Globalization;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Utils;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosShared.Protos;
|
||||
@@ -38,7 +37,7 @@ public partial class MareHub
|
||||
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);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
@@ -18,7 +17,6 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
[Authorize]
|
||||
public partial class MareHub : Hub<IMareHub>, IMareHub
|
||||
{
|
||||
private readonly MareMetrics _mareMetrics;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Grpc.Core;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServer.Services;
|
||||
|
||||
public class GrpcClientIdentificationService : IHostedService
|
||||
public class GrpcClientIdentificationService : GrpcBaseService
|
||||
{
|
||||
private readonly string _shardName;
|
||||
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
||||
@@ -22,14 +22,11 @@ public class GrpcClientIdentificationService : IHostedService
|
||||
private readonly MareMetrics _metrics;
|
||||
protected readonly ConcurrentDictionary<string, UidWithIdent> OnlineClients = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<string, UidWithIdent> RemoteCachedIdents = new(StringComparer.Ordinal);
|
||||
private bool _grpcIsFaulty = false;
|
||||
private ConcurrentQueue<IdentChange> _identChangeQueue = new();
|
||||
private CancellationTokenSource _streamCts = new();
|
||||
private CancellationTokenSource _faultCheckCts = new();
|
||||
|
||||
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient,
|
||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamOut,
|
||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IConfiguration configuration)
|
||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IConfiguration configuration) : base(logger)
|
||||
{
|
||||
var config = configuration.GetSection("MareSynchronos");
|
||||
_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)
|
||||
{
|
||||
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
|
||||
{
|
||||
var result = await toExecute.ConfigureAwait(false);
|
||||
|
||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
SetGrpcFaulty();
|
||||
|
||||
return default;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||
protected override async Task StopAsyncInternal(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await toExecute.ConfigureAwait(false);
|
||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
SetGrpcFaulty();
|
||||
}
|
||||
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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);
|
||||
var msg = new ServerIdentMessage();
|
||||
msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage()
|
||||
{
|
||||
UidWithIdent = c.Value
|
||||
}));
|
||||
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false);
|
||||
_logger.LogInformation("GRPC connection is restored");
|
||||
_grpcIsFaulty = false;
|
||||
}
|
||||
UidWithIdent = c.Value
|
||||
}));
|
||||
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(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;
|
||||
_logger.LogWarning("GRPC connection is faulty");
|
||||
ServerId = _shardName
|
||||
}).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 MareSynchronosServer.Utils;
|
||||
using MareSynchronosServer.RequirementHandlers;
|
||||
using MareSynchronosShared.Services;
|
||||
|
||||
namespace MareSynchronosServer;
|
||||
|
||||
@@ -119,8 +120,10 @@ public class Startup
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<GrpcAuthenticationService>();
|
||||
services.AddSingleton<GrpcClientIdentificationService>();
|
||||
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
|
||||
services.AddHostedService(p => p.GetService<GrpcAuthenticationService>());
|
||||
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
|
||||
|
||||
services.AddDbContextPool<MareDbContext>(options =>
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Authentication;
|
||||
|
||||
public class FailedAuthorization : IDisposable
|
||||
internal class FailedAuthorization : IDisposable
|
||||
{
|
||||
private int failedAttempts = 1;
|
||||
public int FailedAttempts => failedAttempts;
|
||||
|
||||
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServices.Authentication;
|
||||
|
||||
public class SecretKeyAuthenticationHandler
|
||||
internal class SecretKeyAuthenticationHandler
|
||||
{
|
||||
private readonly ILogger<SecretKeyAuthenticationHandler> logger;
|
||||
private readonly MareMetrics metrics;
|
||||
@@ -60,7 +60,7 @@ public class SecretKeyAuthenticationHandler
|
||||
if (string.IsNullOrEmpty(secretKey))
|
||||
{
|
||||
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)
|
||||
@@ -86,7 +86,7 @@ public class SecretKeyAuthenticationHandler
|
||||
}, token);
|
||||
|
||||
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);
|
||||
@@ -152,7 +152,7 @@ public class SecretKeyAuthenticationHandler
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -163,7 +163,7 @@ public class SecretKeyAuthenticationHandler
|
||||
|
||||
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)
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices;
|
||||
|
||||
public class CleanupService : IHostedService, IDisposable
|
||||
internal class CleanupService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly MareMetrics metrics;
|
||||
private readonly SecretKeyAuthenticationHandler _authService;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -101,10 +100,10 @@ internal class IdentityHandler
|
||||
{
|
||||
identChanges[serverId] = new ConcurrentQueue<IdentChange>();
|
||||
}
|
||||
}
|
||||
|
||||
internal record ServerIdentity
|
||||
{
|
||||
public string ServerId { get; set; } = string.Empty;
|
||||
public string CharacterIdent { get; set; } = string.Empty;
|
||||
internal record ServerIdentity
|
||||
{
|
||||
public string ServerId { get; set; } = string.Empty;
|
||||
public string CharacterIdent { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Services;
|
||||
|
||||
public class AuthenticationService : AuthService.AuthServiceBase
|
||||
internal class AuthenticationService : AuthService.AuthServiceBase
|
||||
{
|
||||
private readonly ILogger<AuthenticationService> _logger;
|
||||
private readonly MareDbContext _dbContext;
|
||||
@@ -20,12 +20,16 @@ public class AuthenticationService : AuthService.AuthServiceBase
|
||||
_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);
|
||||
_authHandler.RemoveAuthentication(request.Uid);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using MareSynchronosServer;
|
||||
using MareSynchronosShared.Protos;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -14,13 +14,13 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
|
||||
{
|
||||
public const string AuthScheme = "SecretKeyGrpcAuth";
|
||||
|
||||
private readonly AuthService.AuthServiceClient _authClient;
|
||||
private readonly GrpcAuthenticationService _grpcAuthService;
|
||||
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)
|
||||
{
|
||||
this._authClient = authClient;
|
||||
this._grpcAuthService = authClient;
|
||||
_accessor = accessor;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -44,7 +44,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.NameIdentifier, uid),
|
||||
new(ClaimTypes.NameIdentifier, uid.Uid),
|
||||
new(ClaimTypes.Authentication, authHeader)
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using MareSynchronosShared.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MareSynchronosShared.Data;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosShared.Models;
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class GroupBan
|
||||
{
|
||||
|
||||
@@ -5,8 +5,8 @@ option csharp_namespace = "MareSynchronosShared.Protos";
|
||||
package mareservices;
|
||||
|
||||
service AuthService {
|
||||
rpc Authorize (AuthRequest) returns (AuthReply);
|
||||
rpc RemoveAuth (RemoveAuthRequest) returns (Empty);
|
||||
rpc Authorize (stream AuthRequest) returns (stream AuthReply);
|
||||
rpc RemoveAuth (UidMessage) returns (Empty);
|
||||
rpc ClearUnauthorized (Empty) returns (Empty);
|
||||
}
|
||||
|
||||
@@ -101,10 +101,6 @@ message FileSizeResponse {
|
||||
map<string, int64> hashToFileSize = 1;
|
||||
}
|
||||
|
||||
message RemoveAuthRequest {
|
||||
string uid = 1;
|
||||
}
|
||||
|
||||
message AuthRequest {
|
||||
string ip = 1;
|
||||
string secretKey = 2;
|
||||
@@ -112,5 +108,5 @@ message AuthRequest {
|
||||
|
||||
message AuthReply {
|
||||
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.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosShared.Utils;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,6 +3,7 @@ using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@@ -47,6 +48,7 @@ public class Startup
|
||||
}
|
||||
};
|
||||
|
||||
services.AddSingleton<GrpcAuthenticationService>();
|
||||
services.AddSingleton(new MareMetrics(new List<string> {
|
||||
}, new List<string>
|
||||
{
|
||||
@@ -72,6 +74,7 @@ public class Startup
|
||||
}, mareSettings.GetValue("DbContextPoolSize", 1024));
|
||||
|
||||
services.AddHostedService<CleanupService>();
|
||||
services.AddHostedService(p => p.GetService<GrpcAuthenticationService>());
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user