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>
112 lines
4.3 KiB
C#
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);
|
|
}
|
|
}
|