Deal.Api/Http -> Services/Models/Extensions; Contracts/Integrations и SharedKernel/Tenants -> Abstractions/Models; extension-классы telegram/ml -> Extensions. namespace/using/FQN мигрированы, using дедуплицированы.
63 lines
3.3 KiB
C#
63 lines
3.3 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Middleware операторской сессии: читает httpOnly-куку deal_operator_session, разрешает сессию через
|
|
/// OperatorAuthService и наполняет <c>HttpContext.Items["CurrentOperator"]</c> (Ruling 1 этапа 7).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Зеркало <see cref="SessionMiddleware"/> для операторов: отдельная кука и отдельный ключ Items —
|
|
/// операторская сессия не может подменить тенантную и наоборот (разные имена куки, разные middleware).
|
|
/// Tenant-контекст (ITenantContext/CurrentUser) middleware не трогает — оператор не принадлежит тенанту.
|
|
/// Middleware НЕ отвечает 401 сама (pass-through): ручки /api/operator/*, требующие оператора, проверяют
|
|
/// <c>GetCurrentOperator()</c> и выставляют 401. OperatorAuthService — scoped, поэтому на запрос
|
|
/// создаётся собственный scope через RequestServices (как в SessionMiddleware).
|
|
/// </remarks>
|
|
public sealed class OperatorSessionMiddleware
|
|
{
|
|
private readonly RequestDelegate _next;
|
|
private readonly IOptionsMonitor<OperatorCookieOptions> _cookieOptions;
|
|
|
|
public OperatorSessionMiddleware(RequestDelegate next, IOptionsMonitor<OperatorCookieOptions> cookieOptions)
|
|
{
|
|
_next = next;
|
|
_cookieOptions = cookieOptions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обрабатывает запрос: разрешает операторскую сессию по куке и наполняет контекст.
|
|
/// </summary>
|
|
/// <param name="context">Контекст запроса.</param>
|
|
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<OperatorAuthService>();
|
|
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);
|
|
}
|
|
}
|