using Deal.Api.Configuration; using Deal.Api.Extensions; using Deal.Api.Models; using Deal.Api.Services; using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; using Deal.Modules.Tenants.Application.Models; using Deal.Modules.Tenants.Application.Registrars; using Deal.Modules.Tenants.Application.Services; using Microsoft.Extensions.Options; using Deal.Api.Dtos; // Имя конфигурационного типа совпадает с Microsoft.AspNetCore.Http.CookieOptions — фиксируем алиасом. using OperatorCookieOptions = Deal.Api.Configuration.OperatorCookieOptions; namespace Deal.Api.Middleware; /// /// Middleware операторской сессии: читает httpOnly-куку deal_operator_session, разрешает сессию через /// OperatorAuthService и наполняет HttpContext.Items["CurrentOperator"] (Ruling 1 этапа 7). /// /// /// Зеркало для операторов: отдельная кука и отдельный ключ Items — /// операторская сессия не может подменить тенантную и наоборот (разные имена куки, разные middleware). /// Tenant-контекст (ITenantContext/CurrentUser) middleware не трогает — оператор не принадлежит тенанту. /// Middleware НЕ отвечает 401 сама (pass-through): ручки /api/operator/*, требующие оператора, проверяют /// GetCurrentOperator() и выставляют 401. OperatorAuthService — scoped, поэтому на запрос /// создаётся собственный scope через RequestServices (как в SessionMiddleware). /// public sealed class OperatorSessionMiddleware { private readonly RequestDelegate _next; private readonly IOptionsMonitor _cookieOptions; public OperatorSessionMiddleware(RequestDelegate next, IOptionsMonitor cookieOptions) { _next = next; _cookieOptions = cookieOptions; } /// /// Обрабатывает запрос: разрешает операторскую сессию по куке и наполняет контекст. /// /// Контекст запроса. public async Task InvokeAsync(HttpContext context) { var cookieName = _cookieOptions.CurrentValue.Name; if (context.Request.Cookies.TryGetValue(cookieName, out var rawToken) && !string.IsNullOrWhiteSpace(rawToken)) { // OperatorAuthService scoped: создаём scope на запрос через RequestServices. await using var scope = context.RequestServices.CreateAsyncScope(); var operatorAuthService = scope.ServiceProvider.GetRequiredService(); var identity = await operatorAuthService.ResolveSessionAsync(rawToken, context.RequestAborted); if (identity is not null) { context.SetCurrentOperator(new CurrentOperator(identity.Id, identity.Login, identity.Status)); } } await _next(context); } }