From 3b7db39cc2d6a54eebcff3fa59e2b9920ea3b8a0 Mon Sep 17 00:00:00 2001 From: WorkClub Automation Date: Thu, 5 Mar 2026 19:22:21 +0100 Subject: [PATCH] fix(backend): update middleware ordering and interceptors for RLS Update TenantValidationMiddleware, Program.cs startup sequence, SaveChangesTenantInterceptor, and TenantProvider to ensure proper middleware ordering and tenant context initialization before database access. Co-authored-by: Sisyphus --- .../Middleware/TenantValidationMiddleware.cs | 26 +++++++++---------- backend/WorkClub.Api/Program.cs | 12 +-------- .../SaveChangesTenantInterceptor.cs | 11 ++++---- .../Services/TenantProvider.cs | 14 +++------- 4 files changed, 23 insertions(+), 40 deletions(-) diff --git a/backend/WorkClub.Api/Middleware/TenantValidationMiddleware.cs b/backend/WorkClub.Api/Middleware/TenantValidationMiddleware.cs index 2eddd04..6d5a07c 100644 --- a/backend/WorkClub.Api/Middleware/TenantValidationMiddleware.cs +++ b/backend/WorkClub.Api/Middleware/TenantValidationMiddleware.cs @@ -5,14 +5,17 @@ namespace WorkClub.Api.Middleware; public class TenantValidationMiddleware { private readonly RequestDelegate _next; + private readonly ILogger _logger; - public TenantValidationMiddleware(RequestDelegate next) + public TenantValidationMiddleware(RequestDelegate next, ILogger logger) { _next = next; + _logger = logger; } public async Task InvokeAsync(HttpContext context) { + _logger.LogInformation("TenantValidationMiddleware: Processing request for {Path}", context.Request.Path); if (!context.User.Identity?.IsAuthenticated ?? true) { await _next(context); @@ -37,25 +40,22 @@ public class TenantValidationMiddleware 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; - } + // Parse comma-separated club UUIDs + var clubIds = clubsClaim.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(id => id.Trim()) + .ToArray(); - if (clubsDict == null || !clubsDict.ContainsKey(requestedTenantId)) + if (clubIds.Length == 0 || !clubIds.Contains(requestedTenantId)) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsJsonAsync(new { error = $"User is not a member of tenant {requestedTenantId}" }); return; } + // Store validated tenant ID in HttpContext.Items for downstream middleware/services + context.Items["TenantId"] = requestedTenantId; + _logger.LogInformation("TenantValidationMiddleware: Set TenantId={TenantId} in HttpContext.Items", requestedTenantId); + await _next(context); } } diff --git a/backend/WorkClub.Api/Program.cs b/backend/WorkClub.Api/Program.cs index 84bd50d..643d04f 100644 --- a/backend/WorkClub.Api/Program.cs +++ b/backend/WorkClub.Api/Program.cs @@ -1,4 +1,3 @@ -using Finbuckle.MultiTenant; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; @@ -19,14 +18,6 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); -builder.Services.AddMultiTenant() - .WithHeaderStrategy("X-Tenant-Id") - .WithClaimStrategy("tenant_id") - .WithInMemoryStore(options => - { - options.IsCaseSensitive = false; - }); - builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -36,7 +27,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(); +builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) @@ -93,7 +84,6 @@ if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); app.UseAuthentication(); -app.UseMultiTenant(); app.UseMiddleware(); app.UseAuthorization(); app.UseMiddleware(); diff --git a/backend/WorkClub.Infrastructure/Data/Interceptors/SaveChangesTenantInterceptor.cs b/backend/WorkClub.Infrastructure/Data/Interceptors/SaveChangesTenantInterceptor.cs index 5fc24b8..5f1f543 100644 --- a/backend/WorkClub.Infrastructure/Data/Interceptors/SaveChangesTenantInterceptor.cs +++ b/backend/WorkClub.Infrastructure/Data/Interceptors/SaveChangesTenantInterceptor.cs @@ -1,5 +1,4 @@ -using Finbuckle.MultiTenant; -using Finbuckle.MultiTenant.Abstractions; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; @@ -9,14 +8,14 @@ namespace WorkClub.Infrastructure.Data.Interceptors; public class SaveChangesTenantInterceptor : SaveChangesInterceptor { - private readonly IMultiTenantContextAccessor _tenantAccessor; + private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; public SaveChangesTenantInterceptor( - IMultiTenantContextAccessor tenantAccessor, + IHttpContextAccessor httpContextAccessor, ILogger logger) { - _tenantAccessor = tenantAccessor; + _httpContextAccessor = httpContextAccessor; _logger = logger; } @@ -42,7 +41,7 @@ public class SaveChangesTenantInterceptor : SaveChangesInterceptor if (context == null) return; - var tenantId = _tenantAccessor.MultiTenantContext?.TenantInfo?.Identifier; + var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"] as string; if (string.IsNullOrWhiteSpace(tenantId)) { diff --git a/backend/WorkClub.Infrastructure/Services/TenantProvider.cs b/backend/WorkClub.Infrastructure/Services/TenantProvider.cs index 8d3d788..97af3d9 100644 --- a/backend/WorkClub.Infrastructure/Services/TenantProvider.cs +++ b/backend/WorkClub.Infrastructure/Services/TenantProvider.cs @@ -1,7 +1,5 @@ using System.Security.Claims; using System.Text.Json; -using Finbuckle.MultiTenant; -using Finbuckle.MultiTenant.Abstractions; using Microsoft.AspNetCore.Http; using WorkClub.Application.Interfaces; @@ -9,26 +7,22 @@ namespace WorkClub.Infrastructure.Services; public class TenantProvider : ITenantProvider { - private readonly IMultiTenantContextAccessor _multiTenantContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor; - public TenantProvider( - IMultiTenantContextAccessor multiTenantContextAccessor, - IHttpContextAccessor httpContextAccessor) + public TenantProvider(IHttpContextAccessor httpContextAccessor) { - _multiTenantContextAccessor = multiTenantContextAccessor; _httpContextAccessor = httpContextAccessor; } public string GetTenantId() { - var tenantInfo = _multiTenantContextAccessor.MultiTenantContext?.TenantInfo; - if (tenantInfo == null || string.IsNullOrEmpty(tenantInfo.Identifier)) + var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"] as string; + if (string.IsNullOrEmpty(tenantId)) { throw new InvalidOperationException("Tenant context is not available"); } - return tenantInfo.Identifier; + return tenantId; } public string GetUserRole()