fix(backend): Remove TenantId injection vulnerability in CreateProjectCommand

CRITICAL SECURITY FIX: Removed client-provided TenantId parameter from
CreateProjectCommand to prevent tenant impersonation attacks.

Changes:
- Removed TenantId property from CreateProjectCommand
- Injected ITenantContext into CreateProjectCommandHandler
- Now retrieves authenticated TenantId from JWT token via TenantContext
- Prevents malicious users from creating projects under other tenants

Security Impact:
- Before: Client could provide any TenantId (HIGH RISK)
- After: TenantId extracted from authenticated JWT token (SECURE)

Note: CreateEpic, CreateStory, and CreateTask commands were already secure
as they inherit TenantId from parent entities loaded via Global Query Filters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-04 19:50:15 +01:00
parent 6a70933886
commit 99bd92a3ca
2 changed files with 9 additions and 4 deletions

View File

@@ -8,7 +8,6 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
/// </summary>
public sealed record CreateProjectCommand : IRequest<ProjectDto>
{
public Guid TenantId { get; init; }
public string Name { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public string Key { get; init; } = string.Empty;

View File

@@ -1,5 +1,6 @@
using MediatR;
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
using ColaFlow.Modules.ProjectManagement.Application.Common.Interfaces;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
@@ -12,14 +13,19 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
/// </summary>
public sealed class CreateProjectCommandHandler(
IProjectRepository projectRepository,
IUnitOfWork unitOfWork)
IUnitOfWork unitOfWork,
ITenantContext tenantContext)
: IRequestHandler<CreateProjectCommand, ProjectDto>
{
private readonly IProjectRepository _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
private readonly IUnitOfWork _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
private readonly ITenantContext _tenantContext = tenantContext ?? throw new ArgumentNullException(nameof(tenantContext));
public async Task<ProjectDto> Handle(CreateProjectCommand request, CancellationToken cancellationToken)
{
// Get authenticated tenant ID from JWT token
var tenantId = _tenantContext.GetCurrentTenantId();
// Check if project key already exists
var existingProject = await _projectRepository.GetByKeyAsync(request.Key, cancellationToken);
if (existingProject != null)
@@ -27,9 +33,9 @@ public sealed class CreateProjectCommandHandler(
throw new DomainException($"Project with key '{request.Key}' already exists");
}
// Create project aggregate
// Create project aggregate with authenticated tenant ID
var project = Project.Create(
TenantId.From(request.TenantId),
TenantId.From(tenantId),
request.Name,
request.Description,
request.Key,