using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace ScadaLink.InboundAPI; /// /// Endpoint filter applied to POST /api/{methodName} that enforces two /// cross-cutting guards before the request handler runs: /// /// /// /// InboundAPI-008 — active-node gating. The inbound API is central-active-node-only; /// a standby node returns HTTP 503 so it never executes method scripts. /// /// /// InboundAPI-006 — request body size cap. Oversized bodies are rejected with HTTP /// 413 before being buffered into a JsonDocument. /// /// /// public sealed class InboundApiEndpointFilter : IEndpointFilter { private readonly ILogger _logger; private readonly InboundApiOptions _options; public InboundApiEndpointFilter( ILogger logger, IOptions options) { _logger = logger; _options = options.Value; } public async ValueTask InvokeAsync( EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var httpContext = context.HttpContext; // InboundAPI-008: refuse to serve the inbound API on a standby central node. // The gate is optional — when no IActiveNodeGate is registered (non-clustered // host / tests) the API is served, preserving prior behaviour. var gate = httpContext.RequestServices.GetService(); if (gate is { IsActiveNode: false }) { _logger.LogWarning( "Inbound API request rejected — this node is a standby (not the active central node)"); return Results.Json( new { error = "Inbound API is only available on the active central node" }, statusCode: StatusCodes.Status503ServiceUnavailable); } // InboundAPI-006: cap the request body size. Reject an over-limit body up // front via Content-Length; also lower the per-request max body size so a // chunked/unknown-length stream is cut off by Kestrel as it is read. var maxBytes = _options.MaxRequestBodyBytes; if (httpContext.Request.ContentLength is { } declaredLength && declaredLength > maxBytes) { _logger.LogWarning( "Inbound API request rejected — body length {Length} exceeds limit {Limit}", declaredLength, maxBytes); return Results.Json( new { error = "Request body too large" }, statusCode: StatusCodes.Status413PayloadTooLarge); } var sizeFeature = httpContext.Features.Get(); if (sizeFeature is { IsReadOnly: false }) { sizeFeature.MaxRequestBodySize = maxBytes; } return await next(context); } }