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 { /// /// Constants for the agent API-key authentication scheme / authorization policy. /// 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 { } /// /// 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. /// public class AgentApiKeyAuthenticationHandler : AuthenticationHandler { private readonly IAgentRepo _agentRepo; public AgentApiKeyAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IAgentRepo agentRepo) : base(options, logger, encoder) { _agentRepo = agentRepo; } protected override async Task 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); } } }