80 lines
3.2 KiB
C#
80 lines
3.2 KiB
C#
using System.Security.Claims;
|
|
using System.Text.Encodings.Web;
|
|
using Fitness_core_BE.Helpers;
|
|
using Fitness_core_BE.Interfaces;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Fitness_core_BE.Auth
|
|
{
|
|
/// <summary>
|
|
/// Constants for the agent API-key authentication scheme / authorization policy.
|
|
/// </summary>
|
|
public static class AgentApiKeyDefaults
|
|
{
|
|
public const string AuthenticationScheme = "AgentApiKey";
|
|
public const string PolicyName = "AgentApiKey";
|
|
public const string HeaderName = "X-Api-Key";
|
|
|
|
// Claim types used to carry the resolved agent + client server-side.
|
|
public const string AgentIdClaim = "AgentId";
|
|
public const string ClientIdClaim = "FKClientId";
|
|
}
|
|
|
|
public class AgentApiKeyAuthenticationOptions : AuthenticationSchemeOptions { }
|
|
|
|
/// <summary>
|
|
/// Authenticates on-prem agents using the high-entropy key sent in the X-Api-Key header.
|
|
/// The key is hashed and matched against an active Agent. On success it exposes the
|
|
/// agent's FKClientId and AgentId via claims + HttpContext.Items so downstream code never
|
|
/// has to trust client/agent identifiers coming from the request body.
|
|
/// </summary>
|
|
public class AgentApiKeyAuthenticationHandler : AuthenticationHandler<AgentApiKeyAuthenticationOptions>
|
|
{
|
|
private readonly IAgentRepo _agentRepo;
|
|
|
|
public AgentApiKeyAuthenticationHandler(
|
|
IOptionsMonitor<AgentApiKeyAuthenticationOptions> options,
|
|
ILoggerFactory logger,
|
|
UrlEncoder encoder,
|
|
IAgentRepo agentRepo)
|
|
: base(options, logger, encoder)
|
|
{
|
|
_agentRepo = agentRepo;
|
|
}
|
|
|
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
|
{
|
|
if (!Request.Headers.TryGetValue(AgentApiKeyDefaults.HeaderName, out var headerValues))
|
|
return AuthenticateResult.NoResult();
|
|
|
|
var apiKey = headerValues.FirstOrDefault();
|
|
if (string.IsNullOrWhiteSpace(apiKey))
|
|
return AuthenticateResult.Fail("Missing API key.");
|
|
|
|
var hash = ApiKeyHelper.HashApiKey(apiKey);
|
|
var agent = await _agentRepo.GetByApiKeyHashAsync(hash);
|
|
|
|
if (agent == null || !agent.IsActive)
|
|
return AuthenticateResult.Fail("Invalid API key.");
|
|
|
|
// Expose resolved identity server-side (never trust the body for these).
|
|
Context.Items[AgentApiKeyDefaults.AgentIdClaim] = agent.AgentId.ToString();
|
|
Context.Items[AgentApiKeyDefaults.ClientIdClaim] = agent.FKClientId.ToString();
|
|
|
|
var claims = new[]
|
|
{
|
|
new Claim(AgentApiKeyDefaults.AgentIdClaim, agent.AgentId.ToString()),
|
|
new Claim(AgentApiKeyDefaults.ClientIdClaim, agent.FKClientId.ToString()),
|
|
new Claim(ClaimTypes.NameIdentifier, agent.AgentId.ToString())
|
|
};
|
|
|
|
var identity = new ClaimsIdentity(claims, Scheme.Name);
|
|
var principal = new ClaimsPrincipal(identity);
|
|
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
|
|
|
return AuthenticateResult.Success(ticket);
|
|
}
|
|
}
|
|
}
|