feat(backend): Implement User Invitation System (Phase 4)

Add complete user invitation system to enable multi-user tenants.

Changes:
- Created Invitation domain entity with 7-day expiration
- Implemented InviteUserCommand with security validation
- Implemented AcceptInvitationCommand (creates user + assigns role)
- Implemented GetPendingInvitationsQuery
- Implemented CancelInvitationCommand
- Added TenantInvitationsController with tenant-scoped endpoints
- Added public invitation acceptance endpoint to AuthController
- Created database migration for invitations table
- Registered InvitationRepository in DI container
- Created domain event handlers for audit trail

Security Features:
- Cannot invite as TenantOwner or AIAgent roles
- Cross-tenant validation on all endpoints
- Secure token generation and hashing
- RequireTenantAdmin policy for invite/list
- RequireTenantOwner policy for cancel

This UNBLOCKS 3 skipped Day 6 tests (RemoveUserFromTenant).

🤖 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-03 22:02:56 +01:00
parent 1cf0ef0d9c
commit 4594ebef84
26 changed files with 1736 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations.Events;
using MediatR;
using Microsoft.Extensions.Logging;
namespace ColaFlow.Modules.Identity.Application.EventHandlers;
/// <summary>
/// Event handler for InvitationAcceptedEvent - logs acceptance
/// </summary>
public class InvitationAcceptedEventHandler : INotificationHandler<InvitationAcceptedEvent>
{
private readonly ILogger<InvitationAcceptedEventHandler> _logger;
public InvitationAcceptedEventHandler(ILogger<InvitationAcceptedEventHandler> logger)
{
_logger = logger;
}
public Task Handle(InvitationAcceptedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation(
"Invitation accepted: Email={Email}, Tenant={TenantId}, Role={Role}",
notification.Email,
notification.TenantId,
notification.Role);
// Future: Could send welcome email, track conversion metrics, etc.
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,30 @@
using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations.Events;
using MediatR;
using Microsoft.Extensions.Logging;
namespace ColaFlow.Modules.Identity.Application.EventHandlers;
/// <summary>
/// Event handler for InvitationCancelledEvent - logs cancellation
/// </summary>
public class InvitationCancelledEventHandler : INotificationHandler<InvitationCancelledEvent>
{
private readonly ILogger<InvitationCancelledEventHandler> _logger;
public InvitationCancelledEventHandler(ILogger<InvitationCancelledEventHandler> logger)
{
_logger = logger;
}
public Task Handle(InvitationCancelledEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation(
"Invitation cancelled: Email={Email}, Tenant={TenantId}",
notification.Email,
notification.TenantId);
// Future: Could notify invited user, track cancellation metrics, etc.
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,32 @@
using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations.Events;
using MediatR;
using Microsoft.Extensions.Logging;
namespace ColaFlow.Modules.Identity.Application.EventHandlers;
/// <summary>
/// Event handler for UserInvitedEvent - logs invitation
/// </summary>
public class UserInvitedEventHandler : INotificationHandler<UserInvitedEvent>
{
private readonly ILogger<UserInvitedEventHandler> _logger;
public UserInvitedEventHandler(ILogger<UserInvitedEventHandler> logger)
{
_logger = logger;
}
public Task Handle(UserInvitedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation(
"User invited: Email={Email}, Tenant={TenantId}, Role={Role}, InvitedBy={InvitedBy}",
notification.Email,
notification.TenantId,
notification.Role,
notification.InvitedBy);
// Future: Could add analytics tracking, audit log entry, etc.
return Task.CompletedTask;
}
}