2026-03-03 14:27:30 +01:00
|
|
|
using System.Security.Claims;
|
|
|
|
|
using Microsoft.AspNetCore.Authentication;
|
2026-03-05 19:22:13 +01:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using WorkClub.Domain.Enums;
|
|
|
|
|
using WorkClub.Infrastructure.Data;
|
2026-03-03 14:27:30 +01:00
|
|
|
|
|
|
|
|
namespace WorkClub.Api.Auth;
|
|
|
|
|
|
|
|
|
|
public class ClubRoleClaimsTransformation : IClaimsTransformation
|
|
|
|
|
{
|
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
2026-03-05 19:22:13 +01:00
|
|
|
private readonly AppDbContext _context;
|
2026-03-03 14:27:30 +01:00
|
|
|
|
2026-03-05 19:22:13 +01:00
|
|
|
public ClubRoleClaimsTransformation(
|
|
|
|
|
IHttpContextAccessor httpContextAccessor,
|
|
|
|
|
AppDbContext context)
|
2026-03-03 14:27:30 +01:00
|
|
|
{
|
|
|
|
|
_httpContextAccessor = httpContextAccessor;
|
2026-03-05 19:22:13 +01:00
|
|
|
_context = context;
|
2026-03-03 14:27:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
|
|
|
|
|
{
|
|
|
|
|
if (principal.Identity is not ClaimsIdentity identity || !identity.IsAuthenticated)
|
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(principal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var clubsClaim = principal.FindFirst("clubs")?.Value;
|
|
|
|
|
if (string.IsNullOrEmpty(clubsClaim))
|
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(principal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 19:22:13 +01:00
|
|
|
// Parse comma-separated club UUIDs
|
|
|
|
|
var clubIds = clubsClaim.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
|
|
|
|
.Select(id => id.Trim())
|
|
|
|
|
.ToArray();
|
2026-03-03 14:27:30 +01:00
|
|
|
|
2026-03-05 19:22:13 +01:00
|
|
|
if (clubIds.Length == 0)
|
2026-03-03 14:27:30 +01:00
|
|
|
{
|
|
|
|
|
return Task.FromResult(principal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tenantId = _httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].FirstOrDefault();
|
2026-03-05 19:22:13 +01:00
|
|
|
if (string.IsNullOrEmpty(tenantId) || !clubIds.Contains(tenantId))
|
2026-03-03 14:27:30 +01:00
|
|
|
{
|
|
|
|
|
return Task.FromResult(principal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 19:22:13 +01:00
|
|
|
var userIdClaim = principal.FindFirst("preferred_username")?.Value;
|
|
|
|
|
if (string.IsNullOrEmpty(userIdClaim))
|
2026-03-03 14:27:30 +01:00
|
|
|
{
|
|
|
|
|
return Task.FromResult(principal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 19:22:13 +01:00
|
|
|
// 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));
|
|
|
|
|
}
|
2026-03-03 14:27:30 +01:00
|
|
|
|
|
|
|
|
return Task.FromResult(principal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 19:22:13 +01:00
|
|
|
private ClubRole? GetMemberRole(string externalUserId, string tenantId)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var member = _context.Members
|
|
|
|
|
.FirstOrDefault(m => m.Email == externalUserId && m.TenantId == tenantId);
|
|
|
|
|
|
|
|
|
|
return member?.Role;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string MapClubRoleToAspNetRole(ClubRole clubRole)
|
2026-03-03 14:27:30 +01:00
|
|
|
{
|
2026-03-05 19:22:13 +01:00
|
|
|
return clubRole switch
|
2026-03-03 14:27:30 +01:00
|
|
|
{
|
2026-03-05 19:22:13 +01:00
|
|
|
ClubRole.Admin => "Admin",
|
|
|
|
|
ClubRole.Manager => "Manager",
|
|
|
|
|
ClubRole.Member => "Member",
|
|
|
|
|
ClubRole.Viewer => "Viewer",
|
2026-03-03 14:27:30 +01:00
|
|
|
_ => "Viewer"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|