feat(backend): Implement Day 6 Role Management API
Add complete role management functionality for tenant administrators to manage user roles within their tenants.
Changes:
- Extended IUserTenantRoleRepository with pagination, role counting, and last owner check methods
- Extended IUserRepository with GetByIdAsync(Guid) and GetByIdsAsync for flexible user retrieval
- Extended IRefreshTokenRepository with GetByUserAndTenantAsync and UpdateRangeAsync
- Implemented repository methods in Infrastructure layer
- Created DTOs: UserWithRoleDto and PagedResultDto<T>
- Implemented ListTenantUsersQuery with pagination support
- Implemented AssignUserRoleCommand to assign/update user roles
- Implemented RemoveUserFromTenantCommand with token revocation
- Created TenantUsersController with 4 endpoints (list, assign, remove, get-roles)
- Added comprehensive PowerShell test script
Security Features:
- Only TenantOwner can assign/update/remove roles
- Prevents removal of last TenantOwner (lockout protection)
- Prevents manual assignment of AIAgent role (reserved for MCP)
- Cross-tenant access protection
- Automatic refresh token revocation when user removed
API Endpoints:
- GET /api/tenants/{id}/users - List users with roles (paginated)
- POST /api/tenants/{id}/users/{userId}/role - Assign/update role
- DELETE /api/tenants/{id}/users/{userId} - Remove user from tenant
- GET /api/tenants/roles - Get available roles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
using ColaFlow.Modules.Identity.Application.Commands.AssignUserRole;
|
||||
using ColaFlow.Modules.Identity.Application.Commands.RemoveUserFromTenant;
|
||||
using ColaFlow.Modules.Identity.Application.Queries.ListTenantUsers;
|
||||
using ColaFlow.Modules.Identity.Application.Dtos;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ColaFlow.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/tenants/{tenantId}/users")]
|
||||
[Authorize]
|
||||
public class TenantUsersController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public TenantUsersController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all users in a tenant with their roles
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "RequireTenantAdmin")]
|
||||
public async Task<IActionResult> ListUsers(
|
||||
[FromRoute] Guid tenantId,
|
||||
[FromQuery] int pageNumber = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] string? search = null)
|
||||
{
|
||||
var query = new ListTenantUsersQuery(tenantId, pageNumber, pageSize, search);
|
||||
var result = await _mediator.Send(query);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign or update a user's role in the tenant
|
||||
/// </summary>
|
||||
[HttpPost("{userId}/role")]
|
||||
[Authorize(Policy = "RequireTenantOwner")]
|
||||
public async Task<IActionResult> AssignRole(
|
||||
[FromRoute] Guid tenantId,
|
||||
[FromRoute] Guid userId,
|
||||
[FromBody] AssignRoleRequest request)
|
||||
{
|
||||
var command = new AssignUserRoleCommand(tenantId, userId, request.Role);
|
||||
await _mediator.Send(command);
|
||||
return Ok(new { Message = "Role assigned successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a user from the tenant
|
||||
/// </summary>
|
||||
[HttpDelete("{userId}")]
|
||||
[Authorize(Policy = "RequireTenantOwner")]
|
||||
public async Task<IActionResult> RemoveUser(
|
||||
[FromRoute] Guid tenantId,
|
||||
[FromRoute] Guid userId)
|
||||
{
|
||||
var command = new RemoveUserFromTenantCommand(tenantId, userId);
|
||||
await _mediator.Send(command);
|
||||
return Ok(new { Message = "User removed from tenant successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get available roles
|
||||
/// </summary>
|
||||
[HttpGet("../roles")]
|
||||
[Authorize(Policy = "RequireTenantAdmin")]
|
||||
public IActionResult GetAvailableRoles()
|
||||
{
|
||||
var roles = new[]
|
||||
{
|
||||
new { Name = "TenantOwner", Description = "Full control over the tenant" },
|
||||
new { Name = "TenantAdmin", Description = "Manage users and projects" },
|
||||
new { Name = "TenantMember", Description = "Create and edit tasks" },
|
||||
new { Name = "TenantGuest", Description = "Read-only access" }
|
||||
};
|
||||
|
||||
return Ok(roles);
|
||||
}
|
||||
}
|
||||
|
||||
public record AssignRoleRequest(string Role);
|
||||
Reference in New Issue
Block a user