56 lines
2.4 KiB
C#
56 lines
2.4 KiB
C#
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<object>
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
}
|