using ColaFlow.Modules.Identity.Application.Commands.AssignUserRole; using ColaFlow.Modules.Identity.Application.Commands.RemoveUserFromTenant; using ColaFlow.Modules.Identity.Application.Commands.UpdateUserRole; 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(IMediator mediator) : ControllerBase { /// /// List all users in a tenant with their roles /// [HttpGet] [Authorize(Policy = "RequireTenantAdmin")] public async Task ListUsers( [FromRoute] Guid tenantId, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20, [FromQuery] string? search = null) { // SECURITY: Validate user belongs to target tenant var userTenantIdClaim = User.FindFirst("tenant_id")?.Value; if (userTenantIdClaim == null) return Unauthorized(new { error = "Tenant information not found in token" }); var userTenantId = Guid.Parse(userTenantIdClaim); if (userTenantId != tenantId) return StatusCode(403, new { error = "Access denied: You can only manage users in your own tenant" }); var query = new ListTenantUsersQuery(tenantId, pageNumber, pageSize, search); var result = await mediator.Send(query); return Ok(result); } /// /// Assign or update a user's role in the tenant /// [HttpPost("{userId}/role")] [Authorize(Policy = "RequireTenantOwner")] public async Task AssignRole( [FromRoute] Guid tenantId, [FromRoute] Guid userId, [FromBody] AssignRoleRequest request) { // SECURITY: Validate user belongs to target tenant var userTenantIdClaim = User.FindFirst("tenant_id")?.Value; if (userTenantIdClaim == null) return Unauthorized(new { error = "Tenant information not found in token" }); var userTenantId = Guid.Parse(userTenantIdClaim); if (userTenantId != tenantId) return StatusCode(403, new { error = "Access denied: You can only manage users in your own tenant" }); // Extract current user ID from claims var currentUserIdClaim = User.FindFirst("user_id")?.Value; if (currentUserIdClaim == null) return Unauthorized(new { error = "User ID not found in token" }); var currentUserId = Guid.Parse(currentUserIdClaim); var command = new AssignUserRoleCommand(tenantId, userId, request.Role, currentUserId); await mediator.Send(command); return Ok(new { Message = "Role assigned successfully" }); } /// /// Update an existing user's role in the tenant (RESTful PUT endpoint) /// [HttpPut("{userId:guid}/role")] [Authorize(Policy = "RequireTenantOwner")] public async Task> UpdateRole( [FromRoute] Guid tenantId, [FromRoute] Guid userId, [FromBody] AssignRoleRequest request) { // SECURITY: Validate user belongs to target tenant var userTenantIdClaim = User.FindFirst("tenant_id")?.Value; if (userTenantIdClaim == null) return Unauthorized(new { error = "Tenant information not found in token" }); var userTenantId = Guid.Parse(userTenantIdClaim); if (userTenantId != tenantId) return StatusCode(403, new { error = "Access denied: You can only manage users in your own tenant" }); // Extract current user ID from claims var currentUserIdClaim = User.FindFirst("user_id")?.Value; if (currentUserIdClaim == null) return Unauthorized(new { error = "User ID not found in token" }); var currentUserId = Guid.Parse(currentUserIdClaim); try { var command = new UpdateUserRoleCommand(tenantId, userId, request.Role, currentUserId); var result = await mediator.Send(command); return Ok(result); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } } /// /// Remove a user from the tenant /// [HttpDelete("{userId}")] [Authorize(Policy = "RequireTenantOwner")] public async Task RemoveUser( [FromRoute] Guid tenantId, [FromRoute] Guid userId) { // SECURITY: Validate user belongs to target tenant var userTenantIdClaim = User.FindFirst("tenant_id")?.Value; if (userTenantIdClaim == null) return Unauthorized(new { error = "Tenant information not found in token" }); var userTenantId = Guid.Parse(userTenantIdClaim); if (userTenantId != tenantId) return StatusCode(403, new { error = "Access denied: You can only manage users in your own tenant" }); // Extract current user ID from claims var currentUserIdClaim = User.FindFirst("user_id")?.Value; if (currentUserIdClaim == null) return Unauthorized(new { error = "User ID not found in token" }); var currentUserId = Guid.Parse(currentUserIdClaim); var command = new RemoveUserFromTenantCommand(tenantId, userId, currentUserId, null); await mediator.Send(command); return Ok(new { Message = "User removed from tenant successfully" }); } /// /// Get available roles (Note: This endpoint doesn't use tenantId from route, so tenant validation is skipped. /// It only returns static role definitions, not tenant-specific data.) /// [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);