79 lines
2.7 KiB
C#
79 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.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<ApiKeySchemeOptions>
|
|
{
|
|
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
|
|
private readonly HybridCache HybridCache;
|
|
|
|
public const string CacheKeyFormat = $"Moonlight.Api.{nameof(ApiKeySchemeHandler)}.{{0}}";
|
|
|
|
public ApiKeySchemeHandler(
|
|
IOptionsMonitor<ApiKeySchemeOptions> options,
|
|
ILoggerFactory logger,
|
|
UrlEncoder encoder,
|
|
DatabaseRepository<ApiKey> apiKeyRepository,
|
|
HybridCache hybridCache
|
|
) : base(options, logger, encoder)
|
|
{
|
|
ApiKeyRepository = apiKeyRepository;
|
|
HybridCache = hybridCache;
|
|
}
|
|
|
|
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);
|
|
|
|
var apiKey = await HybridCache.GetOrCreateAsync<ApiKeySession?>(
|
|
cacheKey,
|
|
async ct =>
|
|
{
|
|
return await ApiKeyRepository
|
|
.Query()
|
|
.Where(x => x.Key == authHeaderValue)
|
|
.Select(x => new ApiKeySession(x.Permissions, x.ValidUntil))
|
|
.FirstOrDefaultAsync(cancellationToken: ct);
|
|
},
|
|
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);
|
|
} |