rework authentication
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Authentication;
|
||||
|
||||
internal class FailedAuthorization
|
||||
{
|
||||
private int failedAttempts = 1;
|
||||
public int FailedAttempts => failedAttempts;
|
||||
public Task ResetTask { get; set; }
|
||||
public void IncreaseFailedAttempts()
|
||||
{
|
||||
Interlocked.Increment(ref failedAttempts);
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServices.Authentication;
|
||||
|
||||
public class SecretKeyAuthenticationHandler
|
||||
{
|
||||
private readonly ILogger<SecretKeyAuthenticationHandler> _logger;
|
||||
private readonly MareMetrics _metrics;
|
||||
private const string Unauthorized = "Unauthorized";
|
||||
private readonly ConcurrentDictionary<string, string> _cachedAuthorizations = new();
|
||||
private readonly ConcurrentDictionary<string, FailedAuthorization?> _failedAuthorizations = new();
|
||||
private readonly int _failedAttemptsForTempBan;
|
||||
private readonly int _tempBanMinutes;
|
||||
private readonly List<string> _whitelistedIps = new();
|
||||
|
||||
public void ClearUnauthorizedUsers()
|
||||
{
|
||||
foreach (var item in _cachedAuthorizations.ToArray())
|
||||
{
|
||||
if (item.Value == Unauthorized)
|
||||
{
|
||||
_cachedAuthorizations.TryRemove(item.Key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAuthentication(string uid)
|
||||
{
|
||||
var authorization = _cachedAuthorizations.Where(u => u.Value == uid);
|
||||
if (authorization.Any())
|
||||
{
|
||||
_cachedAuthorizations.Remove(authorization.First().Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuthReply> AuthenticateAsync(MareDbContext mareDbContext, string ip, string secretKey)
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationRequests);
|
||||
|
||||
if (string.IsNullOrEmpty(secretKey))
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||
}
|
||||
|
||||
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization) && existingFailedAuthorization.FailedAttempts > _failedAttemptsForTempBan)
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||
|
||||
if (existingFailedAuthorization.ResetTask == null)
|
||||
{
|
||||
_logger.LogWarning("TempBan {ip} for authorization spam", ip);
|
||||
|
||||
existingFailedAuthorization.ResetTask = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(_tempBanMinutes)).ConfigureAwait(false);
|
||||
|
||||
}).ContinueWith((t) =>
|
||||
{
|
||||
_failedAuthorizations.Remove(ip, out _);
|
||||
});
|
||||
}
|
||||
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||
}
|
||||
|
||||
var hashedHeader = StringUtils.Sha256String(secretKey);
|
||||
|
||||
bool fromCache = _cachedAuthorizations.TryGetValue(hashedHeader, out string uid);
|
||||
|
||||
if (fromCache && !string.IsNullOrEmpty(uid))
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
|
||||
|
||||
if (uid == Unauthorized)
|
||||
{
|
||||
return AuthenticationFailure(ip);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uid = (await mareDbContext.Auth.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.HashedKey == hashedHeader).ConfigureAwait(false))?.UserUID;
|
||||
|
||||
if (string.IsNullOrEmpty(uid))
|
||||
{
|
||||
_cachedAuthorizations[hashedHeader] = Unauthorized;
|
||||
|
||||
return AuthenticationFailure(ip);
|
||||
}
|
||||
|
||||
_cachedAuthorizations[hashedHeader] = uid;
|
||||
}
|
||||
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
|
||||
|
||||
return new AuthReply() { Success = true, Uid = new UidMessage() { Uid = uid } };
|
||||
}
|
||||
|
||||
private AuthReply AuthenticationFailure(string ip)
|
||||
{
|
||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||
|
||||
_logger.LogWarning("Failed authorization from {ip}", ip);
|
||||
if (!_whitelistedIps.Any(w => ip.Contains(w)))
|
||||
{
|
||||
if (_failedAuthorizations.TryGetValue(ip, out var auth))
|
||||
{
|
||||
auth.IncreaseFailedAttempts();
|
||||
}
|
||||
else
|
||||
{
|
||||
_failedAuthorizations[ip] = new FailedAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
|
||||
}
|
||||
|
||||
public SecretKeyAuthenticationHandler(IConfiguration configuration, ILogger<SecretKeyAuthenticationHandler> logger, MareMetrics metrics)
|
||||
{
|
||||
_logger = logger;
|
||||
_metrics = metrics;
|
||||
var config = configuration.GetRequiredSection("MareSynchronos");
|
||||
_failedAttemptsForTempBan = config.GetValue<int>("FailedAuthForTempBan", 5);
|
||||
logger.LogInformation("FailedAuthForTempBan: {num}", _failedAttemptsForTempBan);
|
||||
_tempBanMinutes = config.GetValue<int>("TempBanDurationInMinutes", 30);
|
||||
logger.LogInformation("TempBanMinutes: {num}", _tempBanMinutes);
|
||||
_whitelistedIps = config.GetSection("WhitelistedIps").Get<List<string>>();
|
||||
foreach (var ip in _whitelistedIps)
|
||||
{
|
||||
logger.LogInformation("Whitelisted IP: " + ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using MareSynchronosServices.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosShared.Utils;
|
||||
@@ -19,16 +18,14 @@ namespace MareSynchronosServices;
|
||||
public class CleanupService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly MareMetrics metrics;
|
||||
private readonly SecretKeyAuthenticationHandler _authService;
|
||||
private readonly ILogger<CleanupService> _logger;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IConfiguration _configuration;
|
||||
private Timer? _timer;
|
||||
|
||||
public CleanupService(MareMetrics metrics, SecretKeyAuthenticationHandler authService, ILogger<CleanupService> logger, IServiceProvider services, IConfiguration configuration)
|
||||
public CleanupService(MareMetrics metrics, ILogger<CleanupService> logger, IServiceProvider services, IConfiguration configuration)
|
||||
{
|
||||
this.metrics = metrics;
|
||||
_authService = authService;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
_configuration = configuration.GetRequiredSection("MareSynchronos");
|
||||
@@ -119,8 +116,6 @@ public class CleanupService : IHostedService, IDisposable
|
||||
_logger.LogWarning(ex, "Error during Temp Invite purge");
|
||||
}
|
||||
|
||||
_authService.ClearUnauthorizedUsers();
|
||||
|
||||
_logger.LogInformation($"Cleanup complete");
|
||||
|
||||
dbContext.SaveChanges();
|
||||
@@ -137,8 +132,6 @@ public class CleanupService : IHostedService, IDisposable
|
||||
dbContext.Remove(lodestone);
|
||||
}
|
||||
|
||||
_authService.RemoveAuthentication(user.UID);
|
||||
|
||||
var auth = dbContext.Auth.Single(a => a.UserUID == user.UID);
|
||||
|
||||
var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList();
|
||||
|
||||
@@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Discord.WebSocket;
|
||||
using System.Linq;
|
||||
using Prometheus;
|
||||
using MareSynchronosServices.Authentication;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosServices.Identity;
|
||||
using MareSynchronosShared.Metrics;
|
||||
@@ -304,9 +303,6 @@ public class MareModule : InteractionModuleBase
|
||||
|
||||
await db.Auth.AddAsync(auth).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
var authHandler = scope.ServiceProvider.GetService<SecretKeyAuthenticationHandler>();
|
||||
authHandler.RemoveAuthentication(existingLodestoneAuth.User.UID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using Grpc.Core;
|
||||
using MareSynchronosServices.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Services;
|
||||
|
||||
internal class AuthenticationService : AuthService.AuthServiceBase
|
||||
{
|
||||
private readonly ILogger<AuthenticationService> _logger;
|
||||
private readonly MareDbContext _dbContext;
|
||||
private readonly SecretKeyAuthenticationHandler _authHandler;
|
||||
|
||||
public AuthenticationService(ILogger<AuthenticationService> logger, MareDbContext dbContext, SecretKeyAuthenticationHandler authHandler)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbContext = dbContext;
|
||||
_authHandler = authHandler;
|
||||
}
|
||||
|
||||
public override async Task Authorize(IAsyncStreamReader<AuthRequest> requestStream, IServerStreamWriter<AuthReply> responseStream, ServerCallContext context)
|
||||
{
|
||||
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(UidMessage request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogInformation("Removing Authentication for {uid}", request.Uid);
|
||||
_authHandler.RemoveAuthentication(request.Uid);
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
|
||||
public override Task<Empty> ClearUnauthorized(Empty request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogInformation("Clearing unauthorized users");
|
||||
_authHandler.ClearUnauthorizedUsers();
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using MareSynchronosServices.Authentication;
|
||||
using MareSynchronosServices.Discord;
|
||||
using MareSynchronosServices.Services;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@@ -45,7 +43,6 @@ public class Startup
|
||||
}));
|
||||
|
||||
services.AddTransient(_ => Configuration);
|
||||
services.AddSingleton<SecretKeyAuthenticationHandler>();
|
||||
services.AddSingleton<DiscordBotServices>();
|
||||
services.AddSingleton<IdentityHandler>();
|
||||
services.AddSingleton<CleanupService>();
|
||||
@@ -63,7 +60,6 @@ public class Startup
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGrpcService<AuthenticationService>();
|
||||
endpoints.MapGrpcService<IdentityService>();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user