books/backend/Books.Api/Authorization/CompanyContextMiddleware.cs
Nicolaj Hartmann 1f75c5d791 Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:

Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess

Commands & Handlers:
- Full CQRS command structure for all domains

Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories

GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries

Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)

Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00

72 lines
2.4 KiB
C#

using Books.Api.EventFlow.Repositories;
namespace Books.Api.Authorization;
/// <summary>
/// Middleware that extracts the X-Company-Id header and validates user access.
/// Stores the result in HttpContext.Items for use by GraphQL resolvers.
/// </summary>
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);
}
}
/// <summary>
/// Extension methods for accessing CompanyContext.
/// </summary>
public static class CompanyContextExtensions
{
/// <summary>
/// Get the CompanyContext from HttpContext.
/// </summary>
public static CompanyContext GetCompanyContext(this HttpContext context)
{
return context.Items[CompanyContextMiddleware.ContextKey] as CompanyContext
?? new CompanyContext();
}
/// <summary>
/// Add the CompanyContext middleware to the pipeline.
/// </summary>
public static IApplicationBuilder UseCompanyContext(this IApplicationBuilder app)
{
return app.UseMiddleware<CompanyContextMiddleware>();
}
}