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(); } }