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:
@@ -8,7 +8,6 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed record CreateProjectCommand : IRequest<ProjectDto>
|
public sealed record CreateProjectCommand : IRequest<ProjectDto>
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; init; }
|
|
||||||
public string Name { get; init; } = string.Empty;
|
public string Name { get; init; } = string.Empty;
|
||||||
public string Description { get; init; } = string.Empty;
|
public string Description { get; init; } = string.Empty;
|
||||||
public string Key { get; init; } = string.Empty;
|
public string Key { get; init; } = string.Empty;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
|
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.Aggregates.ProjectAggregate;
|
||||||
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||||
@@ -12,14 +13,19 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CreateProjectCommandHandler(
|
public sealed class CreateProjectCommandHandler(
|
||||||
IProjectRepository projectRepository,
|
IProjectRepository projectRepository,
|
||||||
IUnitOfWork unitOfWork)
|
IUnitOfWork unitOfWork,
|
||||||
|
ITenantContext tenantContext)
|
||||||
: IRequestHandler<CreateProjectCommand, ProjectDto>
|
: IRequestHandler<CreateProjectCommand, ProjectDto>
|
||||||
{
|
{
|
||||||
private readonly IProjectRepository _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
private readonly IProjectRepository _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
|
||||||
private readonly IUnitOfWork _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
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)
|
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
|
// Check if project key already exists
|
||||||
var existingProject = await _projectRepository.GetByKeyAsync(request.Key, cancellationToken);
|
var existingProject = await _projectRepository.GetByKeyAsync(request.Key, cancellationToken);
|
||||||
if (existingProject != null)
|
if (existingProject != null)
|
||||||
@@ -27,9 +33,9 @@ public sealed class CreateProjectCommandHandler(
|
|||||||
throw new DomainException($"Project with key '{request.Key}' already exists");
|
throw new DomainException($"Project with key '{request.Key}' already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create project aggregate
|
// Create project aggregate with authenticated tenant ID
|
||||||
var project = Project.Create(
|
var project = Project.Create(
|
||||||
TenantId.From(request.TenantId),
|
TenantId.From(tenantId),
|
||||||
request.Name,
|
request.Name,
|
||||||
request.Description,
|
request.Description,
|
||||||
request.Key,
|
request.Key,
|
||||||
|
|||||||
Reference in New Issue
Block a user