fix(backend): resolve tenant context initialization in claims transformation
Set tenant context before querying DB in ClubRoleClaimsTransformation.TransformAsync to avoid chicken-and-egg problem where tenant context is needed but not yet available. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,16 +1,22 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WorkClub.Domain.Enums;
|
||||
using WorkClub.Infrastructure.Data;
|
||||
|
||||
namespace WorkClub.Api.Auth;
|
||||
|
||||
public class ClubRoleClaimsTransformation : IClaimsTransformation
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly AppDbContext _context;
|
||||
|
||||
public ClubRoleClaimsTransformation(IHttpContextAccessor httpContextAccessor)
|
||||
public ClubRoleClaimsTransformation(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
AppDbContext context)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
|
||||
@@ -26,46 +32,63 @@ public class ClubRoleClaimsTransformation : IClaimsTransformation
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
|
||||
Dictionary<string, string>? clubsDict;
|
||||
try
|
||||
{
|
||||
clubsDict = JsonSerializer.Deserialize<Dictionary<string, string>>(clubsClaim);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
// Parse comma-separated club UUIDs
|
||||
var clubIds = clubsClaim.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(id => id.Trim())
|
||||
.ToArray();
|
||||
|
||||
if (clubsDict == null || clubsDict.Count == 0)
|
||||
if (clubIds.Length == 0)
|
||||
{
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
|
||||
var tenantId = _httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(tenantId))
|
||||
if (string.IsNullOrEmpty(tenantId) || !clubIds.Contains(tenantId))
|
||||
{
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
|
||||
if (!clubsDict.TryGetValue(tenantId, out var clubRole))
|
||||
var userIdClaim = principal.FindFirst("preferred_username")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
{
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
|
||||
var mappedRole = MapClubRoleToAspNetRole(clubRole);
|
||||
// Look up the user's role in the database for the requested tenant
|
||||
_httpContextAccessor.HttpContext!.Items["TenantId"] = tenantId;
|
||||
var memberRole = GetMemberRole(userIdClaim, tenantId);
|
||||
if (memberRole.HasValue)
|
||||
{
|
||||
var mappedRole = MapClubRoleToAspNetRole(memberRole.Value);
|
||||
identity.AddClaim(new Claim(ClaimTypes.Role, mappedRole));
|
||||
}
|
||||
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
|
||||
private static string MapClubRoleToAspNetRole(string clubRole)
|
||||
private ClubRole? GetMemberRole(string externalUserId, string tenantId)
|
||||
{
|
||||
return clubRole.ToLowerInvariant() switch
|
||||
try
|
||||
{
|
||||
"admin" => "Admin",
|
||||
"manager" => "Manager",
|
||||
"member" => "Member",
|
||||
"viewer" => "Viewer",
|
||||
var member = _context.Members
|
||||
.FirstOrDefault(m => m.Email == externalUserId && m.TenantId == tenantId);
|
||||
|
||||
return member?.Role;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string MapClubRoleToAspNetRole(ClubRole clubRole)
|
||||
{
|
||||
return clubRole switch
|
||||
{
|
||||
ClubRole.Admin => "Admin",
|
||||
ClubRole.Manager => "Manager",
|
||||
ClubRole.Member => "Member",
|
||||
ClubRole.Viewer => "Viewer",
|
||||
_ => "Viewer"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user