VEU.WathBody/3-app/2-cli/Server.WatchBody.Web/Middleware/ApiKeyMiddleware.cs

47 lines
1.7 KiB
C#

using Server.WatchBody.Core.Dal.Src.Domain;
using Server.WatchBody.Core.Dal.Src.Repository;
using Server.WatchBody.Web.Services;
namespace Server.WatchBody.Web.Middleware;
/// <summary>
/// Аутентификация X-Api-Key → athlete_id (spec 02-api-key-middleware).
/// /health — без ключа. 401 единым телом: нет ключа / чужой / заблокирован.
/// </summary>
public class ApiKeyMiddleware(RequestDelegate next)
{
public const string AthleteIdItem = "athlete_id";
public async Task InvokeAsync(HttpContext context, IAthleteRepository athletes)
{
// ключ атлета охраняет только телефонный контур /api/v1/*:
// /api/admin — Bearer-контур владельца (m08, 🛡1), статика
// консоли (/admin) и /health — без ключа
if (!context.Request.Path.StartsWithSegments("/api/v1"))
{
await next(context);
return;
}
var key = context.Request.Headers["X-Api-Key"].FirstOrDefault();
Athlete? athlete = null;
if (!string.IsNullOrEmpty(key))
{
var hash = ApiKeyHash.Of(key);
athlete = athletes.Select(new AthleteFilter { ApiKeyHash = hash })
.FirstOrDefault();
}
if (athlete == null || athlete.IsBlocked)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("""{"error":"unauthorized"}""");
return;
}
context.Items[AthleteIdItem] = athlete.Id;
await next(context);
}
}