diff --git a/src/Koogle.Application/Services/UserService.cs b/src/Koogle.Application/Services/UserService.cs
index 7fe97ec..20b588c 100644
--- a/src/Koogle.Application/Services/UserService.cs
+++ b/src/Koogle.Application/Services/UserService.cs
@@ -136,9 +136,13 @@ public class UserService : IUserService
///
public async Task GetByIdentityUserIdAsync(Guid identityUserId, CancellationToken ct = default)
{
- // Domain-Profil laden (SoftDelete Filter wirkt)
+ // Domain-Profil laden mit Club-Memberships (SoftDelete Filter wirkt)
var profile = await _appDb.UserProfiles
.AsNoTracking()
+ .Include(p => p.Clubs)
+ .ThenInclude(c => c.Club)
+ .Include(p => p.Clubs)
+ .ThenInclude(c => c.RoleAssignments)
.SingleOrDefaultAsync(p => p.IdentityUserId == identityUserId, ct);
if (profile == null)
@@ -156,6 +160,16 @@ public class UserService : IUserService
var roles = await _userManager.GetRolesAsync(identity);
dto.Identity.Roles = roles.ToList();
+ // Map club memberships
+ dto.ClubMemberships = profile.Clubs.Select(c => new UserClubMembershipDto
+ {
+ ClubId = c.ClubId,
+ ClubName = c.Club?.Name ?? "",
+ IsDefault = c.IsDefault,
+ AssignedAt = c.AssignedAt,
+ Roles = c.RoleAssignments.Select(ra => ra.RoleName).ToList()
+ }).ToList();
+
return dto;
}
diff --git a/src/Koogle.Infrastructure/DependencyInjection.cs b/src/Koogle.Infrastructure/DependencyInjection.cs
index bdba4fa..3fd9522 100644
--- a/src/Koogle.Infrastructure/DependencyInjection.cs
+++ b/src/Koogle.Infrastructure/DependencyInjection.cs
@@ -69,7 +69,7 @@ public static class DependencyInjection
options.AddPolicy("ClubAdmin", p => p.Requirements.Add(new ClubRoleRequirement("Admin")));
});
services.AddSingleton();
-
+ services.AddSingleton();
// HTTP Context Accessor (für CurrentUserService)
services.AddHttpContextAccessor();
diff --git a/src/Koogle.Infrastructure/Security/ClubRoleRequirement.cs b/src/Koogle.Infrastructure/Security/ClubRoleRequirement.cs
index 7a8da1b..87819eb 100644
--- a/src/Koogle.Infrastructure/Security/ClubRoleRequirement.cs
+++ b/src/Koogle.Infrastructure/Security/ClubRoleRequirement.cs
@@ -14,15 +14,31 @@ public sealed class ClubRoleRequirement : IAuthorizationRequirement
public string RequiredRole { get; }
}
-public sealed class ClubRoleHandler : AuthorizationHandler
+///
+/// Handles ClubRoleRequirement when no resource is passed (e.g., [Authorize(Policy = "ClubViewer")]).
+/// Uses current_club_id claim to determine the club context.
+///
+public sealed class ClubRoleHandler : AuthorizationHandler
{
protected override Task HandleRequirementAsync(
+ AuthorizationHandlerContext context,
+ ClubRoleRequirement requirement)
+ {
+ // Get current club from claims
+ var currentClubClaim = context.User.FindFirst("current_club_id");
+ if (currentClubClaim == null || !Guid.TryParse(currentClubClaim.Value, out var clubId))
+ {
+ return Task.CompletedTask; // No club context, fail
+ }
+
+ return CheckClubRole(context, requirement, clubId);
+ }
+
+ private static Task CheckClubRole(
AuthorizationHandlerContext context,
ClubRoleRequirement requirement,
Guid clubId)
{
- // Hierarchie (optional): Admin >= Editor >= Viewer
- // Du kannst das anpassen/erweitern.
static int Rank(string role) => role switch
{
"Admin" => 3,
@@ -33,7 +49,7 @@ public sealed class ClubRoleHandler : AuthorizationHandler
+/// Handles ClubRoleRequirement when a specific clubId resource is passed.
+///
+public sealed class ClubRoleResourceHandler : AuthorizationHandler
+{
+ protected override Task HandleRequirementAsync(
+ AuthorizationHandlerContext context,
+ ClubRoleRequirement requirement,
+ Guid clubId)
+ {
+ static int Rank(string role) => role switch
+ {
+ "Admin" => 3,
+ "Editor" => 2,
+ "Viewer" => 1,
+ _ => 0
+ };
+
+ var requiredRank = Rank(requirement.RequiredRole);
+
+ var claims = context.User.FindAll("club_role");
+ foreach (var c in claims)
+ {
+ var parts = c.Value.Split(':', 2);
+ if (parts.Length != 2) continue;
+
+ if (Guid.TryParse(parts[0], out var claimClubId) && claimClubId == clubId)
+ {
+ var userRole = parts[1];
+ if (Rank(userRole) >= requiredRank)
+ {
+ context.Succeed(requirement);
+ break;
+ }
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/Koogle.Web/Store/AuthState/AuthEffects.cs b/src/Koogle.Web/Store/AuthState/AuthEffects.cs
index 99f2e38..da767c0 100644
--- a/src/Koogle.Web/Store/AuthState/AuthEffects.cs
+++ b/src/Koogle.Web/Store/AuthState/AuthEffects.cs
@@ -12,21 +12,21 @@ namespace Koogle.Web.Store.AuthState
{
private readonly IUserService _userService;
private readonly IAuthorizationService _authorizationService;
+ private readonly ICurrentClubContext _currentClubContext;
private readonly ILogger _logger;
///
/// Initializes a new instance of the AuthEffects class.
///
- /// The user service.
- /// The authorization service.
- /// The logger.
public AuthEffects(
IUserService userService,
IAuthorizationService authorizationService,
+ ICurrentClubContext currentClubContext,
ILogger logger)
{
_userService = userService;
_authorizationService = authorizationService;
+ _currentClubContext = currentClubContext;
_logger = logger;
}
@@ -50,13 +50,32 @@ namespace Koogle.Web.Store.AuthState
return;
}
- //var roles = await _authorizationService.GetCurrentUserRolesAsync();
- //var companyIds = await _authorizationService.GetAccessibleCompanyIdsAsync();
+ // Merge Identity roles with club-specific roles for current club
+ var roles = currentUser.Identity.Roles.ToList();
+ var currentClubId = _currentClubContext.ClubId;
- dispatcher.Dispatch(new AuthState.InitializeAuthSuccessAction(currentUser, currentUser.Identity.Roles));
+ if (currentClubId != Guid.Empty)
+ {
+ var clubMembership = currentUser.ClubMemberships
+ .FirstOrDefault(m => m.ClubId == currentClubId);
+
+ if (clubMembership != null)
+ {
+ // Add club roles that aren't already in the list
+ foreach (var clubRole in clubMembership.Roles)
+ {
+ if (!roles.Contains(clubRole))
+ {
+ roles.Add(clubRole);
+ }
+ }
+ }
+ }
+
+ dispatcher.Dispatch(new AuthState.InitializeAuthSuccessAction(currentUser, roles));
_logger.LogInformation("Auth initialized for user {DisplayName} with roles {Roles}",
- currentUser.DisplayName, string.Join(", ", currentUser.Identity.Roles));
+ currentUser.DisplayName, string.Join(", ", roles));
}
catch (Exception ex)
{