using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
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 IUnitOfWork _unitOfWork;
private readonly UserManager _userManager;
private readonly ILogger _logger;
public BannedIpsController(
IUnitOfWork unitOfWork,
UserManager userManager,
ILogger logger)
{
_unitOfWork = unitOfWork;
_userManager = userManager;
_logger = logger;
}
/// Lists all banned IPs, showing active and expired separately.
public async Task Index()
{
var bans = (await _unitOfWork.BannedIps.GetAllAsync())
.OrderByDescending(b => b.BannedAt)
.ToList();
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.
///
[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();
if (!System.Net.IPAddress.TryParse(ipAddress, out _))
{
TempData["Error"] = $"'{ipAddress}' is not a valid IP address.";
return RedirectToAction(nameof(Index));
}
var existing = await _unitOfWork.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);
await _unitOfWork.BannedIps.AddAsync(new BannedIp
{
IpAddress = ipAddress,
Reason = reason?.Trim(),
BannedByUserId = currentUser?.Id,
BannedAt = DateTime.UtcNow,
ExpiresAt = expiresAt,
IsActive = true
});
await _unitOfWork.CompleteAsync();
_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.
[HttpPost, ValidateAntiForgeryToken]
public async Task Lift(int id)
{
var ban = await _unitOfWork.BannedIps.GetByIdAsync(id);
if (ban == null)
{
TempData["Error"] = "Ban not found.";
return RedirectToAction(nameof(Index));
}
ban.IsActive = false;
await _unitOfWork.CompleteAsync();
_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.
[HttpPost, ValidateAntiForgeryToken]
public async Task Delete(int id)
{
var ban = await _unitOfWork.BannedIps.GetByIdAsync(id);
if (ban != null)
{
await _unitOfWork.BannedIps.DeleteAsync(ban);
await _unitOfWork.CompleteAsync();
_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.
public IActionResult MyIp()
{
return Json(new { ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown" });
}
}