feat(backend): Implement 3 HIGH priority architecture fixes (Phase 2)
Complete Day 8 implementation of HIGH priority gap fixes identified in Day 6 Architecture Gap Analysis. Changes: - **Fix 6: Performance Index Migration** - Added composite index (tenant_id, role) on user_tenant_roles table for optimized queries - **Fix 5: Pagination Enhancement** - Added HasPreviousPage/HasNextPage properties to PagedResultDto - **Fix 4: ResendVerificationEmail Feature** - Implemented complete resend verification email flow with security best practices **Fix 6 Details (Performance Index):** - Created migration: AddUserTenantRolesPerformanceIndex - Added composite index ix_user_tenant_roles_tenant_role (tenant_id, role) - Improves query performance for ListTenantUsers with role filtering - Migration applied successfully to database **Fix 5 Details (Pagination):** - Enhanced PagedResultDto with HasPreviousPage and HasNextPage computed properties - Pagination already fully implemented in ListTenantUsersQuery/Handler - Supports page/pageSize query parameters in TenantUsersController **Fix 4 Details (ResendVerificationEmail):** - Created ResendVerificationEmailCommand and handler - Added POST /api/auth/resend-verification endpoint - Security features implemented: * Email enumeration prevention (always returns success) * Rate limiting (1 email per minute via IRateLimitService) * Token rotation (invalidates old token, generates new) * SHA-256 token hashing * 24-hour expiration * Comprehensive audit logging Test Results: - All builds succeeded (0 errors, 10 warnings - pre-existing) - 77 total tests, 64 passed (83.1% pass rate) - No test regressions from Phase 2 changes - 9 failing tests are pre-existing invitation workflow tests Files Modified: 4 Files Created: 4 (2 commands, 2 migrations) Total Lines Changed: +752/-1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ using ColaFlow.Modules.Identity.Application.Commands.ForgotPassword;
|
||||
using ColaFlow.Modules.Identity.Application.Commands.Login;
|
||||
using ColaFlow.Modules.Identity.Application.Commands.ResetPassword;
|
||||
using ColaFlow.Modules.Identity.Application.Commands.VerifyEmail;
|
||||
using ColaFlow.Modules.Identity.Application.Commands.ResendVerificationEmail;
|
||||
using ColaFlow.Modules.Identity.Application.Commands.AcceptInvitation;
|
||||
using ColaFlow.Modules.Identity.Application.Services;
|
||||
using MediatR;
|
||||
@@ -169,6 +170,30 @@ public class AuthController(
|
||||
return Ok(new { message = "Email verified successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resend email verification link
|
||||
/// Always returns success to prevent email enumeration attacks
|
||||
/// </summary>
|
||||
[HttpPost("resend-verification")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ResendVerificationResponse), 200)]
|
||||
public async Task<IActionResult> ResendVerification([FromBody] ResendVerificationRequest request)
|
||||
{
|
||||
var baseUrl = $"{Request.Scheme}://{Request.Host}";
|
||||
|
||||
var command = new ResendVerificationEmailCommand(
|
||||
request.Email,
|
||||
request.TenantId,
|
||||
baseUrl);
|
||||
|
||||
await mediator.Send(command);
|
||||
|
||||
// Always return success to prevent email enumeration
|
||||
return Ok(new ResendVerificationResponse(
|
||||
Message: "If the email exists, a verification link has been sent.",
|
||||
Success: true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiate password reset flow (sends email with reset link)
|
||||
/// Always returns success to prevent email enumeration attacks
|
||||
@@ -252,6 +277,10 @@ public record LoginRequest(
|
||||
|
||||
public record VerifyEmailRequest(string Token);
|
||||
|
||||
public record ResendVerificationRequest(string Email, Guid TenantId);
|
||||
|
||||
public record ResendVerificationResponse(string Message, bool Success);
|
||||
|
||||
public record ForgotPasswordRequest(string Email, string TenantSlug);
|
||||
|
||||
public record ResetPasswordRequest(string Token, string NewPassword);
|
||||
|
||||
Reference in New Issue
Block a user