rework authentication

This commit is contained in:
rootdarkarchon
2022-12-18 14:53:44 +01:00
parent f9d9e2608e
commit f278c5a762
17 changed files with 137 additions and 401 deletions

View File

@@ -0,0 +1,3 @@
namespace MareSynchronosShared.Authentication;
internal record SecretKeyAuthReply(bool Success, string? Uid);

View File

@@ -1,27 +1,27 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using MareSynchronosServer;
using MareSynchronosShared.Services;
using MareSynchronosShared.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ISystemClock = Microsoft.AspNetCore.Authentication.ISystemClock;
namespace MareSynchronosShared.Authentication;
public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
public class SecretKeyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string AuthScheme = "SecretKeyGrpcAuth";
private readonly GrpcAuthenticationService _grpcAuthService;
private readonly MareDbContext _mareDbContext;
private readonly IHttpContextAccessor _accessor;
private readonly SecretKeyAuthenticatorService secretKeyAuthenticatorService;
public SecretKeyGrpcAuthenticationHandler(IHttpContextAccessor accessor, GrpcAuthenticationService authClient,
public SecretKeyAuthenticationHandler(IHttpContextAccessor accessor, SecretKeyAuthenticatorService secretKeyAuthenticatorService,
IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
this._grpcAuthService = authClient;
_accessor = accessor;
this.secretKeyAuthenticatorService = secretKeyAuthenticatorService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
@@ -33,22 +33,20 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
var ip = _accessor.GetIpAddress();
var authResult = await _grpcAuthService.AuthorizeAsync(ip, authHeader).ConfigureAwait(false);
var authResult = await secretKeyAuthenticatorService.AuthorizeAsync(ip, authHeader).ConfigureAwait(false);
if (!authResult.Success)
{
return AuthenticateResult.Fail("Failed Authorization");
}
var uid = authResult.Uid;
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, uid.Uid),
new(ClaimTypes.NameIdentifier, authResult.Uid),
new(ClaimTypes.Authentication, authHeader)
};
var identity = new ClaimsIdentity(claims, nameof(SecretKeyGrpcAuthenticationHandler));
var identity = new ClaimsIdentity(claims, nameof(SecretKeyAuthenticationHandler));
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

View File

@@ -0,0 +1,104 @@
using System.Collections.Concurrent;
using MareSynchronosShared.Data;
using MareSynchronosShared.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MareSynchronosShared.Authentication;
public class SecretKeyAuthenticatorService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ILogger<SecretKeyAuthenticatorService> _logger;
private readonly ConcurrentDictionary<string, SecretKeyAuthReply> _cachedPositiveResponses = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, SecretKeyFailedAuthorization?> _failedAuthorizations = new(StringComparer.Ordinal);
private readonly int _failedAttemptsForTempBan;
private readonly int _tempBanMinutes;
private readonly List<string> _whitelistedIps;
public SecretKeyAuthenticatorService(IServiceScopeFactory serviceScopeFactory, IConfiguration configuration, ILogger<SecretKeyAuthenticatorService> logger)
{
_logger = logger;
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);
}
_serviceScopeFactory = serviceScopeFactory;
}
internal async Task<SecretKeyAuthReply> AuthorizeAsync(string ip, string secretKey)
{
if (_cachedPositiveResponses.TryGetValue(secretKey, out var cachedPositiveResponse))
{
return cachedPositiveResponse;
}
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization) && existingFailedAuthorization.FailedAttempts > _failedAttemptsForTempBan)
{
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(Success: false, Uid: null);
}
using var scope = _serviceScopeFactory.CreateScope();
using var context = scope.ServiceProvider.GetService<MareDbContext>();
var hashedHeader = StringUtils.Sha256String(secretKey);
var authReply = await context.Auth.AsNoTracking().SingleOrDefaultAsync(u => u.HashedKey == hashedHeader).ConfigureAwait(false);
SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID);
if (reply.Success)
{
_cachedPositiveResponses[secretKey] = reply;
_ = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
_cachedPositiveResponses.TryRemove(secretKey, out _);
});
}
else
{
return AuthenticationFailure(ip);
}
return reply;
}
private SecretKeyAuthReply AuthenticationFailure(string ip)
{
_logger.LogWarning("Failed authorization from {ip}", ip);
if (!_whitelistedIps.Any(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase)))
{
if (_failedAuthorizations.TryGetValue(ip, out var auth))
{
auth.IncreaseFailedAttempts();
}
else
{
_failedAuthorizations[ip] = new SecretKeyFailedAuthorization();
}
}
return new(Success: false, Uid: null);
}
}

View File

@@ -0,0 +1,12 @@
namespace MareSynchronosShared.Authentication;
internal record SecretKeyFailedAuthorization
{
private int failedAttempts = 1;
public int FailedAttempts => failedAttempts;
public Task ResetTask { get; set; }
public void IncreaseFailedAttempts()
{
Interlocked.Increment(ref failedAttempts);
}
}