diff --git a/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs b/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs index 6531b20..06b1247 100644 --- a/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs +++ b/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs @@ -40,6 +40,10 @@ public static class ModuleExtensions services.AddScoped(); services.AddScoped(); + // Register services + services.AddScoped(); + // Register MediatR handlers from Application assembly (v13.x syntax) services.AddMediatR(cfg => { diff --git a/colaflow-api/src/ColaFlow.API/Hubs/ProjectHub.cs b/colaflow-api/src/ColaFlow.API/Hubs/ProjectHub.cs index 3e0951f..ff5fa1b 100644 --- a/colaflow-api/src/ColaFlow.API/Hubs/ProjectHub.cs +++ b/colaflow-api/src/ColaFlow.API/Hubs/ProjectHub.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.SignalR; +using ColaFlow.Modules.ProjectManagement.Application.Services; namespace ColaFlow.API.Hubs; @@ -7,6 +8,13 @@ namespace ColaFlow.API.Hubs; /// public class ProjectHub : BaseHub { + private readonly IProjectPermissionService _permissionService; + + public ProjectHub(IProjectPermissionService permissionService) + { + _permissionService = permissionService; + } + /// /// Join project room (to receive project-level updates) /// @@ -15,7 +23,14 @@ public class ProjectHub : BaseHub var tenantId = GetCurrentTenantId(); var userId = GetCurrentUserId(); - // TODO: Validate user has permission to access this project + // Validate user has permission to access this project + var hasPermission = await _permissionService.IsUserProjectMemberAsync( + userId, projectId, Context.ConnectionAborted); + + if (!hasPermission) + { + throw new HubException("You do not have permission to access this project"); + } var groupName = GetProjectGroupName(projectId); await Groups.AddToGroupAsync(Context.ConnectionId, groupName); @@ -37,6 +52,16 @@ public class ProjectHub : BaseHub public async Task LeaveProject(Guid projectId) { var userId = GetCurrentUserId(); + + // Validate user has permission to access this project (for consistency) + var hasPermission = await _permissionService.IsUserProjectMemberAsync( + userId, projectId, Context.ConnectionAborted); + + if (!hasPermission) + { + throw new HubException("You do not have permission to access this project"); + } + var groupName = GetProjectGroupName(projectId); await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectPermissionService.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectPermissionService.cs new file mode 100644 index 0000000..cb021d5 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectPermissionService.cs @@ -0,0 +1,18 @@ +namespace ColaFlow.Modules.ProjectManagement.Application.Services; + +/// +/// Service for checking project-level permissions +/// +public interface IProjectPermissionService +{ + /// + /// Checks if a user has permission to access a project + /// Currently checks if user is the project owner + /// TODO: Extend to check ProjectMember table when implemented + /// + /// User ID to check + /// Project ID to check access for + /// Cancellation token + /// True if user has access, false otherwise + Task IsUserProjectMemberAsync(Guid userId, Guid projectId, CancellationToken cancellationToken = default); +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Infrastructure/Services/ProjectPermissionService.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Infrastructure/Services/ProjectPermissionService.cs new file mode 100644 index 0000000..8be51c6 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Infrastructure/Services/ProjectPermissionService.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using ColaFlow.Modules.ProjectManagement.Application.Services; +using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence; + +namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Services; + +/// +/// Implementation of project permission checking service +/// +public sealed class ProjectPermissionService : IProjectPermissionService +{ + private readonly PMDbContext _dbContext; + + public ProjectPermissionService(PMDbContext dbContext) + { + _dbContext = dbContext; + } + + /// + /// Checks if a user has permission to access a project + /// Currently checks if user is the project owner + /// Multi-tenant isolation is enforced by the DbContext query filter + /// TODO: Extend to check ProjectMember table when implemented + /// + public async Task IsUserProjectMemberAsync(Guid userId, Guid projectId, CancellationToken cancellationToken = default) + { + // Query will automatically apply tenant filter from PMDbContext + var project = await _dbContext.Projects + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id.Value == projectId, cancellationToken); + + if (project == null) + { + // Project doesn't exist or user's tenant doesn't have access + return false; + } + + // Check if user is the project owner + // TODO: When ProjectMember table is implemented, also check: + // await _dbContext.ProjectMembers.AnyAsync(pm => pm.ProjectId == projectId && pm.UserId == userId) + return project.OwnerId.Value == userId; + } +}