Switch to JWT authentication (#19)

* switch to jwt authentication

* fix delete files

* adjust saving of deletion of all files

* update api to main/jwt

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
rootdarkarchon
2023-01-02 17:07:34 +01:00
committed by GitHub
parent bdd8830c8e
commit 5f0c12ecfa
15 changed files with 140 additions and 101 deletions

View File

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

View File

@@ -1,75 +0,0 @@
using System.Collections.Concurrent;
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MareSynchronosShared.Authentication;
public class SecretKeyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string AuthScheme = "SecretKeyGrpcAuth";
private readonly IHttpContextAccessor _accessor;
private readonly SecretKeyAuthenticatorService secretKeyAuthenticatorService;
private static readonly ConcurrentDictionary<string, SemaphoreSlim> IPLocks = new(StringComparer.Ordinal);
public SecretKeyAuthenticationHandler(IHttpContextAccessor accessor, SecretKeyAuthenticatorService secretKeyAuthenticatorService,
IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
_accessor = accessor;
this.secretKeyAuthenticatorService = secretKeyAuthenticatorService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
{
return AuthenticateResult.NoResult();
}
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
{
return AuthenticateResult.Fail("Failed Authorization");
}
var ip = _accessor.GetIpAddress();
if (!IPLocks.TryGetValue(ip, out var semaphore))
{
semaphore = new SemaphoreSlim(1);
IPLocks[ip] = semaphore;
}
try
{
await semaphore.WaitAsync(Context.RequestAborted).ConfigureAwait(false);
var authResult = await secretKeyAuthenticatorService.AuthorizeAsync(ip, authHeader).ConfigureAwait(false);
if (!authResult.Success)
{
return AuthenticateResult.Fail("Failed Authorization");
}
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, authResult.Uid),
new(ClaimTypes.Authentication, authHeader)
};
var identity = new ClaimsIdentity(claims, nameof(SecretKeyAuthenticationHandler));
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
finally
{
semaphore.Release();
}
}
}

View File

@@ -6,7 +6,6 @@ using MareSynchronosShared.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MareSynchronosShared.Authentication;
@@ -27,11 +26,11 @@ public class SecretKeyAuthenticatorService
_serviceScopeFactory = serviceScopeFactory;
}
internal async Task<SecretKeyAuthReply> AuthorizeAsync(string ip, string secretKey)
public async Task<SecretKeyAuthReply> AuthorizeAsync(string ip, string hashedSecretKey)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationRequests);
if (_cachedPositiveResponses.TryGetValue(secretKey, out var cachedPositiveResponse))
if (_cachedPositiveResponses.TryGetValue(hashedSecretKey, out var cachedPositiveResponse))
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
return cachedPositiveResponse;
@@ -58,8 +57,7 @@ public class SecretKeyAuthenticatorService
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);
var authReply = await context.Auth.AsNoTracking().SingleOrDefaultAsync(u => u.HashedKey == hashedSecretKey).ConfigureAwait(false);
SecretKeyAuthReply reply = new(authReply != null, authReply?.UserUID);
@@ -67,11 +65,11 @@ public class SecretKeyAuthenticatorService
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
_cachedPositiveResponses[secretKey] = reply;
_cachedPositiveResponses[hashedSecretKey] = reply;
_ = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
_cachedPositiveResponses.TryRemove(secretKey, out _);
_cachedPositiveResponses.TryRemove(hashedSecretKey, out _);
});
}

View File

@@ -11,6 +11,8 @@ public class MareConfigurationAuthBase : MareConfigurationBase
public int TempBanDurationInMinutes { get; set; } = 5;
[RemoteConfiguration]
public List<string> WhitelistedIps { get; set; } = new();
[RemoteConfiguration]
public string Jwt { get; set; } = string.Empty;
public override string ToString()
{
@@ -19,6 +21,7 @@ public class MareConfigurationAuthBase : MareConfigurationBase
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
sb.AppendLine($"{nameof(FailedAuthForTempBan)} => {FailedAuthForTempBan}");
sb.AppendLine($"{nameof(TempBanDurationInMinutes)} => {TempBanDurationInMinutes}");
sb.AppendLine($"{nameof(Jwt)} => {Jwt}");
sb.AppendLine($"{nameof(WhitelistedIps)} => {string.Join(", ", WhitelistedIps)}");
return sb.ToString();
}