using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; using Microsoft.AspNetCore.Authentication; namespace WebApp.Authentication { public class AuthController : Controller { private readonly AuthenticationService _authService; private readonly LoginRateLimitService _rateLimitService; private readonly ILogger _logger; public AuthController( AuthenticationService authService, LoginRateLimitService rateLimitService, ILogger logger) { _authService = authService; _rateLimitService = rateLimitService; _logger = logger; } [HttpPost] [AllowAnonymous] public async Task CookieLogin( [FromForm] string email, [FromForm] string password, [FromForm] bool rememberMe = false, [FromForm] string? returnUrl = null) { try { // Get client IP var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; // Check rate limiting if (_rateLimitService.IsLockedOut(ipAddress)) { var remaining = _rateLimitService.GetRemainingLockoutTime(ipAddress); _logger.LogWarning( "Login attempt from locked out IP: {IpAddress}. Remaining: {Remaining}", ipAddress, remaining); var errorMsg = Uri.EscapeDataString($"Too many failed attempts. Try again in {remaining?.Minutes ?? 15} minutes."); var redirectUrl = string.IsNullOrEmpty(returnUrl) ? $"/login?error={errorMsg}" : $"/login?error={errorMsg}&returnUrl={Uri.EscapeDataString(returnUrl)}"; return Redirect(redirectUrl); } // Validate credentials var result = _authService.ValidateCredentials(email, password); if (!result.IsSuccess) { // Record failed attempt _rateLimitService.RecordFailedAttempt(ipAddress); _logger.LogWarning( "Failed login attempt for {Email} from {IpAddress}", email, ipAddress); var redirectUrl = string.IsNullOrEmpty(returnUrl) ? "/login?error=Invalid%20email%20or%20password." : $"/login?error=Invalid%20email%20or%20password.&returnUrl={Uri.EscapeDataString(returnUrl)}"; return Redirect(redirectUrl); } // Success - clear rate limit tracking _rateLimitService.RecordSuccessfulLogin(ipAddress); // Create claims var claims = new List { new Claim(ClaimTypes.Name, result.DisplayName!), new Claim(ClaimTypes.Email, result.Email!), new Claim(ClaimTypes.Role, result.Role!) }; var claimsIdentity = new ClaimsIdentity(claims, "Auth"); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); // Configure auth properties var authProperties = new AuthenticationProperties { IsPersistent = rememberMe, ExpiresUtc = rememberMe ? DateTimeOffset.UtcNow.AddDays(30) : DateTimeOffset.UtcNow.AddMinutes(20) }; await HttpContext.SignInAsync("Auth", claimsPrincipal, authProperties); _logger.LogInformation( "Successful login for {Email} ({Role}) from {IpAddress}", result.Email, result.Role, ipAddress); // Validate return URL is local to prevent open redirect attacks if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return Redirect("/"); } catch (Exception ex) { _logger.LogError(ex, "Error during login process"); TempData["LoginError"] = "An error occurred. Please try again."; var redirectUrl = string.IsNullOrEmpty(returnUrl) ? "/login" : $"/login?returnUrl={Uri.EscapeDataString(returnUrl)}"; return Redirect(redirectUrl); } } [HttpPost] [Authorize] public async Task CookieLogout() { var userEmail = User.FindFirst(ClaimTypes.Email)?.Value; await HttpContext.SignOutAsync("Auth"); _logger.LogInformation("User {Email} logged out", userEmail); return Redirect("/login"); } } }