using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using PowderCoating.Core.Entities; using PowderCoating.Infrastructure.Data; using PowderCoating.Shared.Constants; namespace PowderCoating.Web.Controllers; /// /// SuperAdmin management of banned IP addresses. /// Entries here block login before Identity even checks credentials. /// [Authorize(Policy = AppConstants.Policies.SuperAdminOnly)] public class BannedIpsController : Controller { private readonly ApplicationDbContext _db; private readonly UserManager _userManager; private readonly ILogger _logger; public BannedIpsController( ApplicationDbContext db, UserManager userManager, ILogger logger) { _db = db; _userManager = userManager; _logger = logger; } /// Lists all banned IPs, showing active and expired separately. // GET: BannedIps public async Task Index() { var bans = await _db.BannedIps .OrderByDescending(b => b.BannedAt) .ToListAsync(); return View(bans); } /// /// Adds a new IP ban. Rejects obviously invalid formats but doesn't require /// a perfect regex — admins are trusted to enter valid IPs. /// // POST: BannedIps/Add [HttpPost] [ValidateAntiForgeryToken] public async Task Add(string ipAddress, string? reason, DateTime? expiresAt) { if (string.IsNullOrWhiteSpace(ipAddress)) { TempData["Error"] = "IP address is required."; return RedirectToAction(nameof(Index)); } ipAddress = ipAddress.Trim(); // Basic sanity check — must look like an IPv4 or IPv6 address if (!System.Net.IPAddress.TryParse(ipAddress, out _)) { TempData["Error"] = $"'{ipAddress}' is not a valid IP address."; return RedirectToAction(nameof(Index)); } // Don't duplicate an active ban for the same IP var existing = await _db.BannedIps.FirstOrDefaultAsync(b => b.IpAddress == ipAddress && b.IsActive); if (existing != null) { TempData["Error"] = $"{ipAddress} already has an active ban (added {existing.BannedAt:MMM dd, yyyy})."; return RedirectToAction(nameof(Index)); } var currentUser = await _userManager.GetUserAsync(User); _db.BannedIps.Add(new BannedIp { IpAddress = ipAddress, Reason = reason?.Trim(), BannedByUserId = currentUser?.Id, BannedAt = DateTime.UtcNow, ExpiresAt = expiresAt, IsActive = true }); await _db.SaveChangesAsync(); _logger.LogWarning("IP {IP} banned by {Admin}. Reason: {Reason}", ipAddress, User.Identity?.Name, reason); TempData["Success"] = $"{ipAddress} has been banned."; return RedirectToAction(nameof(Index)); } /// Lifts a ban immediately by marking IsActive = false. // POST: BannedIps/Lift/5 [HttpPost] [ValidateAntiForgeryToken] public async Task Lift(int id) { var ban = await _db.BannedIps.FindAsync(id); if (ban == null) { TempData["Error"] = "Ban not found."; return RedirectToAction(nameof(Index)); } ban.IsActive = false; await _db.SaveChangesAsync(); _logger.LogInformation("IP ban lifted for {IP} by {Admin}", ban.IpAddress, User.Identity?.Name); TempData["Success"] = $"Ban on {ban.IpAddress} has been lifted."; return RedirectToAction(nameof(Index)); } /// Permanently deletes a ban record. // POST: BannedIps/Delete/5 [HttpPost] [ValidateAntiForgeryToken] public async Task Delete(int id) { var ban = await _db.BannedIps.FindAsync(id); if (ban != null) { _db.BannedIps.Remove(ban); await _db.SaveChangesAsync(); _logger.LogInformation("IP ban record deleted for {IP} by {Admin}", ban.IpAddress, User.Identity?.Name); TempData["Success"] = $"Ban record for {ban.IpAddress} deleted."; } return RedirectToAction(nameof(Index)); } /// Returns the requesting client's IP so the admin can pre-fill it quickly. // GET: BannedIps/MyIp public IActionResult MyIp() { return Json(new { ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown" }); } }