Files
Moonlight/Moonlight.Api/Implementations/ApiKeyScheme/ApiKeySchemeHandler.cs

78 lines
2.7 KiB
C#

using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moonlight.Api.Database;
using Moonlight.Api.Database.Entities;
using Moonlight.Shared;
namespace Moonlight.Api.Implementations.ApiKeyScheme;
public class ApiKeySchemeHandler : AuthenticationHandler<ApiKeySchemeOptions>
{
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
private readonly IMemoryCache MemoryCache;
private const string CacheKeyFormat = $"Moonlight.Api.{nameof(ApiKeySchemeHandler)}.{{0}}";
public ApiKeySchemeHandler(
IOptionsMonitor<ApiKeySchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
DatabaseRepository<ApiKey> apiKeyRepository,
IMemoryCache memoryCache
) : base(options, logger, encoder)
{
ApiKeyRepository = apiKeyRepository;
MemoryCache = memoryCache;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authHeaderValue = Request.Headers.Authorization.FirstOrDefault() ?? null;
if (string.IsNullOrWhiteSpace(authHeaderValue))
return AuthenticateResult.NoResult();
if (authHeaderValue.Length > 32)
return AuthenticateResult.Fail("Invalid api key specified");
var cacheKey = string.Format(CacheKeyFormat, authHeaderValue);
if (!MemoryCache.TryGetValue<ApiKeySession>(cacheKey, out var apiKey))
{
apiKey = await ApiKeyRepository
.Query()
.Where(x => x.Key == authHeaderValue)
.Select(x => new ApiKeySession(x.Permissions, x.ValidUntil))
.FirstOrDefaultAsync();
if (apiKey == null)
return AuthenticateResult.Fail("Invalid api key specified");
MemoryCache.Set(cacheKey, apiKey, Options.LookupCacheTime);
}
else
{
if (apiKey == null)
return AuthenticateResult.Fail("Invalid api key specified");
}
if (DateTimeOffset.UtcNow > apiKey.ValidUntil)
return AuthenticateResult.Fail("Api key expired");
return AuthenticateResult.Success(new AuthenticationTicket(
new ClaimsPrincipal(
new ClaimsIdentity(
apiKey.Permissions.Select(x => new Claim(Permissions.ClaimType, x)).ToArray()
)
),
Scheme.Name
));
}
private record ApiKeySession(string[] Permissions, DateTimeOffset ValidUntil);
}