using System.Security.Claims; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Hybrid; 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 { private readonly DatabaseRepository ApiKeyRepository; private readonly HybridCache HybridCache; public const string CacheKeyFormat = $"Moonlight.Api.{nameof(ApiKeySchemeHandler)}.{{0}}"; public ApiKeySchemeHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, DatabaseRepository apiKeyRepository, HybridCache hybridCache ) : base(options, logger, encoder) { ApiKeyRepository = apiKeyRepository; HybridCache = hybridCache; } protected override async Task 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); var apiKey = await HybridCache.GetOrCreateAsync( cacheKey, async ct => { var x = await ApiKeyRepository .Query() .Where(x => x.Key == authHeaderValue) .Select(x => new ApiKeySession(x.Permissions, x.ValidUntil)) .FirstOrDefaultAsync(cancellationToken: ct); Console.WriteLine($"API: {x?.ValidUntil}"); return x; }, new HybridCacheEntryOptions() { LocalCacheExpiration = Options.LookupL1CacheTime, Expiration = Options.LookupL2CacheTime } ); 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); }