47 lines
1.7 KiB
C#
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);
|
|
}
|
|
}
|