using Server.WatchBody.Core.Dal.Src.Domain; using Server.WatchBody.Core.Dal.Src.Repository; using Server.WatchBody.Web.Services; namespace Server.WatchBody.Web.Middleware; /// /// Аутентификация X-Api-Key → athlete_id (spec 02-api-key-middleware). /// /health — без ключа. 401 единым телом: нет ключа / чужой / заблокирован. /// 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); } }