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.Security.Claims;
|
||||||
using System.Text.Json;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using WorkClub.Domain.Enums;
|
||||||
|
using WorkClub.Infrastructure.Data;
|
||||||
|
|
||||||
namespace WorkClub.Api.Auth;
|
namespace WorkClub.Api.Auth;
|
||||||
|
|
||||||
public class ClubRoleClaimsTransformation : IClaimsTransformation
|
public class ClubRoleClaimsTransformation : IClaimsTransformation
|
||||||
{
|
{
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
public ClubRoleClaimsTransformation(IHttpContextAccessor httpContextAccessor)
|
public ClubRoleClaimsTransformation(
|
||||||
|
IHttpContextAccessor httpContextAccessor,
|
||||||
|
AppDbContext context)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
|
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
|
||||||
@@ -26,46 +32,63 @@ public class ClubRoleClaimsTransformation : IClaimsTransformation
|
|||||||
return Task.FromResult(principal);
|
return Task.FromResult(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<string, string>? clubsDict;
|
// Parse comma-separated club UUIDs
|
||||||
try
|
var clubIds = clubsClaim.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
{
|
.Select(id => id.Trim())
|
||||||
clubsDict = JsonSerializer.Deserialize<Dictionary<string, string>>(clubsClaim);
|
.ToArray();
|
||||||
}
|
|
||||||
catch (JsonException)
|
|
||||||
{
|
|
||||||
return Task.FromResult(principal);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clubsDict == null || clubsDict.Count == 0)
|
if (clubIds.Length == 0)
|
||||||
{
|
{
|
||||||
return Task.FromResult(principal);
|
return Task.FromResult(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenantId = _httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].FirstOrDefault();
|
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);
|
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);
|
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));
|
identity.AddClaim(new Claim(ClaimTypes.Role, mappedRole));
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(principal);
|
return Task.FromResult(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string MapClubRoleToAspNetRole(string clubRole)
|
private ClubRole? GetMemberRole(string externalUserId, string tenantId)
|
||||||
{
|
{
|
||||||
return clubRole.ToLowerInvariant() switch
|
try
|
||||||
{
|
{
|
||||||
"admin" => "Admin",
|
var member = _context.Members
|
||||||
"manager" => "Manager",
|
.FirstOrDefault(m => m.Email == externalUserId && m.TenantId == tenantId);
|
||||||
"member" => "Member",
|
|
||||||
"viewer" => "Viewer",
|
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"
|
_ => "Viewer"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user