using System.Text.Json; namespace WorkClub.Api.Middleware; public class TenantValidationMiddleware { private readonly RequestDelegate _next; public TenantValidationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { if (!context.User.Identity?.IsAuthenticated ?? true) { await _next(context); return; } if (!context.Request.Headers.TryGetValue("X-Tenant-Id", out var tenantIdHeader) || string.IsNullOrWhiteSpace(tenantIdHeader)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsJsonAsync(new { error = "X-Tenant-Id header is required" }); return; } var requestedTenantId = tenantIdHeader.ToString(); var clubsClaim = context.User.FindFirst("clubs")?.Value; if (string.IsNullOrEmpty(clubsClaim)) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsJsonAsync(new { error = "User does not have clubs claim" }); return; } Dictionary? clubsDict; try { clubsDict = JsonSerializer.Deserialize>(clubsClaim); } catch (JsonException) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsJsonAsync(new { error = "Invalid clubs claim format" }); return; } if (clubsDict == null || !clubsDict.ContainsKey(requestedTenantId)) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsJsonAsync(new { error = $"User is not a member of tenant {requestedTenantId}" }); return; } await _next(context); } }