using System.Security.Cryptography; using System.Text; using Fitness_core_BE.Models.IOs; using Microsoft.AspNetCore.Mvc.Filters; namespace Fitness_core_BE.Helpers { // Gates the subscription-billing worker endpoint behind a single shared secret in the // X-Worker-Key header. Distinct from JwtMiddleware/CustomMiddleware (human/agent session // auth) and from PaymentService:InternalApiKey (Core's own outbound key to Payment-Service) — // this authenticates the trusted scheduler calling back into Core. Writes the 401/503 // response directly (like RequiresPermissionAttribute) rather than throwing, because Core // has no global exception handler to turn a thrown exception into the right status code on // this unauthenticated route. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class RequireWorkerKeyAttribute : Attribute, IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var configuration = context.HttpContext.RequestServices.GetService(typeof(IConfiguration)) as IConfiguration; var configuredKey = configuration?["Worker:InternalApiKey"]; if (string.IsNullOrWhiteSpace(configuredKey)) { await WriteErrorAsync(context, 503, "Worker:InternalApiKey is not configured."); return; } var providedKey = context.HttpContext.Request.Headers["X-Worker-Key"].FirstOrDefault(); if (string.IsNullOrWhiteSpace(providedKey) || !FixedTimeEquals(providedKey, configuredKey)) { await WriteErrorAsync(context, 401, "Invalid worker key."); return; } await next(); } private static async Task WriteErrorAsync(ActionExecutingContext context, int statusCode, string message) { context.HttpContext.Response.StatusCode = statusCode; await context.HttpContext.Response.WriteAsJsonAsync(new ApiResponse { StatusCode = statusCode, Message = message, Data = null }); } private static bool FixedTimeEquals(string a, string b) { return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(a), Encoding.UTF8.GetBytes(b)); } } }