feat(backend): Implement complete RBAC system (Day 5 Phase 2)

Implemented Role-Based Access Control (RBAC) with 5 tenant-level roles following Clean Architecture principles.

Changes:
- Created TenantRole enum (TenantOwner, TenantAdmin, TenantMember, TenantGuest, AIAgent)
- Created UserTenantRole entity with repository pattern
- Updated JWT service to include role claims (tenant_role, role)
- Updated RegisterTenant to auto-assign TenantOwner role
- Updated Login to query and include user role in JWT
- Updated RefreshToken to preserve role claims
- Added authorization policies in Program.cs (RequireTenantOwner, RequireTenantAdmin, etc.)
- Updated /api/auth/me endpoint to return role information
- Created EF Core migration for user_tenant_roles table
- Applied database migration successfully

Database:
- New table: identity.user_tenant_roles
- Columns: id, user_id, tenant_id, role, assigned_at, assigned_by_user_id
- Indexes: user_id, tenant_id, role, unique(user_id, tenant_id)
- Foreign keys: CASCADE on user and tenant deletion

Testing:
- Created test-rbac.ps1 PowerShell script
- All RBAC tests passing
- JWT tokens contain role claims
- Role persists across login and token refresh

Documentation:
- DAY5-PHASE2-RBAC-IMPLEMENTATION-SUMMARY.md with complete implementation details

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-03 15:00:39 +01:00
parent 17f3d4a2b3
commit aaab26ba6c
19 changed files with 1714 additions and 16 deletions

View File

@@ -18,7 +18,7 @@ public class JwtService : IJwtService
_configuration = configuration;
}
public string GenerateToken(User user, Tenant tenant)
public string GenerateToken(User user, Tenant tenant, TenantRole tenantRole)
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey not configured")));
@@ -36,7 +36,10 @@ public class JwtService : IJwtService
new("tenant_plan", tenant.Plan.ToString()),
new("full_name", user.FullName.Value),
new("auth_provider", user.AuthProvider.ToString()),
new(ClaimTypes.Role, "User") // TODO: Implement real roles
// Role claims (both standard and custom)
new("tenant_role", tenantRole.ToString()), // Custom claim for application logic
new(ClaimTypes.Role, tenantRole.ToString()) // Standard ASP.NET Core role claim
};
var token = new JwtSecurityToken(

View File

@@ -14,6 +14,7 @@ public class RefreshTokenService : IRefreshTokenService
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IUserRepository _userRepository;
private readonly ITenantRepository _tenantRepository;
private readonly IUserTenantRoleRepository _userTenantRoleRepository;
private readonly IJwtService _jwtService;
private readonly IConfiguration _configuration;
private readonly ILogger<RefreshTokenService> _logger;
@@ -22,6 +23,7 @@ public class RefreshTokenService : IRefreshTokenService
IRefreshTokenRepository refreshTokenRepository,
IUserRepository userRepository,
ITenantRepository tenantRepository,
IUserTenantRoleRepository userTenantRoleRepository,
IJwtService jwtService,
IConfiguration configuration,
ILogger<RefreshTokenService> logger)
@@ -29,6 +31,7 @@ public class RefreshTokenService : IRefreshTokenService
_refreshTokenRepository = refreshTokenRepository;
_userRepository = userRepository;
_tenantRepository = tenantRepository;
_userTenantRoleRepository = userTenantRoleRepository;
_jwtService = jwtService;
_configuration = configuration;
_logger = logger;
@@ -127,8 +130,20 @@ public class RefreshTokenService : IRefreshTokenService
throw new UnauthorizedAccessException("Tenant not found or inactive");
}
// Generate new access token
var newAccessToken = _jwtService.GenerateToken(user, tenant);
// Get user's tenant role
var userTenantRole = await _userTenantRoleRepository.GetByUserAndTenantAsync(
user.Id,
tenant.Id,
cancellationToken);
if (userTenantRole == null)
{
_logger.LogWarning("User {UserId} has no role assigned for tenant {TenantId}", user.Id, tenant.Id);
throw new UnauthorizedAccessException("User role not found");
}
// Generate new access token with role
var newAccessToken = _jwtService.GenerateToken(user, tenant, userTenantRole.Role);
// Generate new refresh token (token rotation)
var newRefreshToken = await GenerateRefreshTokenAsync(user, ipAddress, userAgent, cancellationToken);