using Books.Api.EventFlow.Repositories;
namespace Books.Api.Authorization;
///
/// Middleware that extracts the X-Company-Id header and validates user access.
/// Stores the result in HttpContext.Items for use by GraphQL resolvers.
///
public class CompanyContextMiddleware(RequestDelegate next)
{
public const string HeaderName = "X-Company-Id";
public const string ContextKey = "CompanyContext";
public async Task InvokeAsync(HttpContext context, IUserCompanyAccessRepository accessRepository)
{
var companyContext = new CompanyContext();
// Only process if user is authenticated
if (context.User.Identity?.IsAuthenticated == true)
{
var userId = context.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
if (userId != null && context.Request.Headers.TryGetValue(HeaderName, out var companyIdHeader))
{
var companyId = companyIdHeader.ToString();
if (!string.IsNullOrWhiteSpace(companyId))
{
// Validate user has access to this company
var access = await accessRepository.GetAccessAsync(userId, companyId);
if (access != null)
{
companyContext = new CompanyContext
{
CompanyId = companyId,
Role = access.Role
};
}
}
}
}
// Store in HttpContext.Items for resolvers to access
context.Items[ContextKey] = companyContext;
await next(context);
}
}
///
/// Extension methods for accessing CompanyContext.
///
public static class CompanyContextExtensions
{
///
/// Get the CompanyContext from HttpContext.
///
public static CompanyContext GetCompanyContext(this HttpContext context)
{
return context.Items[CompanyContextMiddleware.ContextKey] as CompanyContext
?? new CompanyContext();
}
///
/// Add the CompanyContext middleware to the pipeline.
///
public static IApplicationBuilder UseCompanyContext(this IApplicationBuilder app)
{
return app.UseMiddleware();
}
}