8.1 ApiKeyMiddleware

Enforce X-Api-Key on /api/* requests with constant-time comparison.
/i/{mac}.png and /health remain unauthenticated. No-op when the
configured key is empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 16:01:25 +02:00
parent 3e01fa7980
commit 475e8988b5
3 changed files with 53 additions and 2 deletions

View File

@@ -0,0 +1,48 @@
using FrameProcessor.Configuration;
using FrameProcessor.Domain;
using Microsoft.Extensions.Options;
namespace FrameProcessor.Middleware;
/// <summary>
/// Enforces an <c>X-Api-Key</c> header on <c>/api/*</c> requests. Other paths
/// (notably <c>/i/{mac}.png</c> and <c>/health</c>) pass through untouched.
/// When the configured key is empty the middleware is a no-op.
/// </summary>
public sealed class ApiKeyMiddleware
{
private const string HeaderName = "X-Api-Key";
private readonly RequestDelegate _next;
private readonly IOptionsMonitor<ApiKeyOptions> _options;
public ApiKeyMiddleware(RequestDelegate next, IOptionsMonitor<ApiKeyOptions> options)
{
_next = next;
_options = options;
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase))
{
await _next(context).ConfigureAwait(false);
return;
}
var configured = _options.CurrentValue.Value;
if (string.IsNullOrEmpty(configured))
{
await _next(context).ConfigureAwait(false);
return;
}
var provided = context.Request.Headers[HeaderName].ToString();
if (!new ApiKey(configured).Matches(provided))
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
await _next(context).ConfigureAwait(false);
}
}

View File

@@ -1,5 +1,6 @@
using FrameProcessor.Configuration;
using FrameProcessor.ImagePipeline;
using FrameProcessor.Middleware;
using FrameProcessor.Mqtt;
using FrameProcessor.Storage;
using FrameProcessor.UrlFetch;
@@ -64,6 +65,8 @@ var app = builder.Build();
// Eagerly resolve FramesRegistry so an invalid frames.json fails startup fast.
_ = app.Services.GetRequiredService<FramesRegistry>();
app.UseMiddleware<ApiKeyMiddleware>();
app.MapControllers();
app.Run();