Files
ColaFlow/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Application/Commands/RegisterTenant/RegisterTenantCommandHandler.cs
Yaojia Wang aaab26ba6c 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>
2025-11-03 15:00:39 +01:00

112 lines
4.3 KiB
C#

using ColaFlow.Modules.Identity.Application.Services;
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
using ColaFlow.Modules.Identity.Domain.Repositories;
using MediatR;
namespace ColaFlow.Modules.Identity.Application.Commands.RegisterTenant;
public class RegisterTenantCommandHandler : IRequestHandler<RegisterTenantCommand, RegisterTenantResult>
{
private readonly ITenantRepository _tenantRepository;
private readonly IUserRepository _userRepository;
private readonly IJwtService _jwtService;
private readonly IPasswordHasher _passwordHasher;
private readonly IRefreshTokenService _refreshTokenService;
private readonly IUserTenantRoleRepository _userTenantRoleRepository;
public RegisterTenantCommandHandler(
ITenantRepository tenantRepository,
IUserRepository userRepository,
IJwtService jwtService,
IPasswordHasher passwordHasher,
IRefreshTokenService refreshTokenService,
IUserTenantRoleRepository userTenantRoleRepository)
{
_tenantRepository = tenantRepository;
_userRepository = userRepository;
_jwtService = jwtService;
_passwordHasher = passwordHasher;
_refreshTokenService = refreshTokenService;
_userTenantRoleRepository = userTenantRoleRepository;
}
public async Task<RegisterTenantResult> Handle(
RegisterTenantCommand request,
CancellationToken cancellationToken)
{
// 1. Validate slug uniqueness
var slug = TenantSlug.Create(request.TenantSlug);
var slugExists = await _tenantRepository.ExistsBySlugAsync(slug, cancellationToken);
if (slugExists)
{
throw new InvalidOperationException($"Tenant slug '{request.TenantSlug}' is already taken");
}
// 2. Create tenant
var plan = Enum.Parse<SubscriptionPlan>(request.SubscriptionPlan);
var tenant = Tenant.Create(
TenantName.Create(request.TenantName),
slug,
plan);
await _tenantRepository.AddAsync(tenant, cancellationToken);
// 3. Create admin user with hashed password
var hashedPassword = _passwordHasher.HashPassword(request.AdminPassword);
var adminUser = User.CreateLocal(
TenantId.Create(tenant.Id),
Email.Create(request.AdminEmail),
hashedPassword,
FullName.Create(request.AdminFullName));
await _userRepository.AddAsync(adminUser, cancellationToken);
// 4. Assign TenantOwner role to admin user
var tenantOwnerRole = UserTenantRole.Create(
UserId.Create(adminUser.Id),
TenantId.Create(tenant.Id),
TenantRole.TenantOwner);
await _userTenantRoleRepository.AddAsync(tenantOwnerRole, cancellationToken);
// 5. Generate JWT token with role
var accessToken = _jwtService.GenerateToken(adminUser, tenant, TenantRole.TenantOwner);
// 6. Generate refresh token
var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(
adminUser,
ipAddress: null,
userAgent: null,
cancellationToken);
// 6. Return result
return new RegisterTenantResult(
new Dtos.TenantDto
{
Id = tenant.Id,
Name = tenant.Name.Value,
Slug = tenant.Slug.Value,
Status = tenant.Status.ToString(),
Plan = tenant.Plan.ToString(),
SsoEnabled = tenant.SsoConfig != null,
SsoProvider = tenant.SsoConfig?.Provider.ToString(),
CreatedAt = tenant.CreatedAt,
UpdatedAt = tenant.UpdatedAt ?? tenant.CreatedAt
},
new Dtos.UserDto
{
Id = adminUser.Id,
TenantId = tenant.Id,
Email = adminUser.Email.Value,
FullName = adminUser.FullName.Value,
Status = adminUser.Status.ToString(),
AuthProvider = adminUser.AuthProvider.ToString(),
IsEmailVerified = adminUser.EmailVerifiedAt.HasValue,
CreatedAt = adminUser.CreatedAt
},
accessToken,
refreshToken);
}
}