Rework Admin UI #6

Merged
MasterMito merged 18 commits from epic/admin_rework_second_try into main 2026-03-20 11:55:38 +01:00
7 changed files with 2344 additions and 9 deletions
Showing only changes of commit 112b299b8e - Show all commits
@@ -44,6 +44,14 @@ public class TenantValidationMiddleware
if (string.IsNullOrEmpty(clubsClaim)) if (string.IsNullOrEmpty(clubsClaim))
{ {
// NEW: Skip check if user is a global admin
var realmAccess = context.User.FindFirst("realm_access")?.Value;
if (!string.IsNullOrEmpty(realmAccess) && realmAccess.Contains("\"admin\"", StringComparison.OrdinalIgnoreCase))
{
await _next(context);
return;
}
context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsJsonAsync(new { error = "User does not have clubs claim" }); await context.Response.WriteAsJsonAsync(new { error = "User does not have clubs claim" });
return; return;
@@ -9,10 +9,12 @@ namespace WorkClub.Api.Services;
public class AdminClubService public class AdminClubService
{ {
private readonly AppDbContext _context; private readonly AppDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AdminClubService(AppDbContext context) public AdminClubService(AppDbContext context, IHttpContextAccessor httpContextAccessor)
{ {
_context = context; _context = context;
_httpContextAccessor = httpContextAccessor;
} }
public async Task<List<ClubDetailDto>> GetAllClubsAsync() public async Task<List<ClubDetailDto>> GetAllClubsAsync()
@@ -33,7 +35,15 @@ public class AdminClubService
public async Task<ClubDetailDto> CreateClubAsync(CreateClubRequest request) public async Task<ClubDetailDto> CreateClubAsync(CreateClubRequest request)
{ {
var tenantId = Guid.NewGuid().ToString(); var tenantId = "club-" + Guid.NewGuid().ToString().Substring(0, 8);
// Ensure interceptors can see the new tenantId
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext != null)
{
httpContext.Items["TenantId"] = tenantId;
}
var club = new Club var club = new Club
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
@@ -185,11 +185,6 @@ public class TenantDbTransactionInterceptor : DbCommandInterceptor, IDbTransacti
{ {
var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"] as string; var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"] as string;
if (string.IsNullOrWhiteSpace(tenantId)) return null; if (string.IsNullOrWhiteSpace(tenantId)) return null;
if (!Guid.TryParse(tenantId, out _))
{
_logger.LogWarning("Invalid tenant ID format: {TenantId}", tenantId);
return null;
}
return tenantId; return tenantId;
} }
@@ -185,7 +185,7 @@ public class ClubEndpointsTests : IntegrationTestBase
} }
[Fact] [Fact]
public async Task GetClubsCurrent_NoTenantContext_ReturnsBadRequest() public async Task GetClubsCurrent_NoTenantContext_ReturnsForbidden()
{ {
AuthenticateAs("admin@test.com", new Dictionary<string, string> AuthenticateAs("admin@test.com", new Dictionary<string, string>
{ {
@@ -194,7 +194,7 @@ public class ClubEndpointsTests : IntegrationTestBase
var response = await Client.GetAsync("/api/clubs/current"); var response = await Client.GetAsync("/api/clubs/current");
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
} }
[Fact] [Fact]
@@ -67,6 +67,7 @@ public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProg
GRANT CONNECT ON DATABASE workclub_test TO rls_test_user; GRANT CONNECT ON DATABASE workclub_test TO rls_test_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO rls_test_user; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO rls_test_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO rls_test_user; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO rls_test_user;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_admin') THEN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_admin') THEN
CREATE ROLE app_admin; CREATE ROLE app_admin;
END IF; END IF;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long