using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Nuuru.Server.Models; using Nuuru.Server.Services; using System.Security.Claims; namespace Nuuru.Server.Middleware { public class BanCheckMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public BanCheckMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context, IBanService banService, UserManager userManager) { // Skip ban check for non-authenticated users if (!context.User.Identity?.IsAuthenticated ?? true) { await _next(context); return; } // Skip ban check for endpoints that allow anonymous access var endpoint = context.GetEndpoint(); if (endpoint?.Metadata.GetMetadata() != null) { await _next(context); return; } // Get user ID from claims var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim == null || !Guid.TryParse(userIdClaim.Value, out var userId)) { await _next(context); return; } var path = context.Request.Path.Value ?? string.Empty; // Allow banned users to access auth and ban appeal endpoints if (path.StartsWith("/api/Auth", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/api/Moderation/bans/mine", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/api/Moderation/bans/appeals", StringComparison.OrdinalIgnoreCase)) { await _next(context); return; } // Check for sitewide ban first if (await banService.IsUserBannedAsync(userId, BanZone.Sitewide)) { var ban = await banService.GetActiveBanForZoneAsync(userId, BanZone.Sitewide); _logger.LogWarning("User {UserId} attempted to access {Path} while sitewide banned", userId, path); await WriteBanResponse(context, ban, "You are banned from this site", BanZone.Sitewide); return; } // Determine zone-specific ban based on route BanZone? routeZone = null; if (path.StartsWith("/api/booru", StringComparison.OrdinalIgnoreCase)) routeZone = BanZone.Booruwide; else if (path.StartsWith("/api/forum", StringComparison.OrdinalIgnoreCase)) routeZone = BanZone.Forumwide; if (routeZone.HasValue && await banService.IsUserBannedAsync(userId, routeZone.Value)) { var ban = await banService.GetActiveBanForZoneAsync(userId, routeZone.Value); var zoneName = routeZone.Value == BanZone.Booruwide ? "the booru" : "the forum"; _logger.LogWarning("User {UserId} attempted to access {Path} while banned from {Zone}", userId, path, routeZone.Value); await WriteBanResponse(context, ban, $"You are banned from {zoneName}", routeZone.Value); return; } await _next(context); } private static async Task WriteBanResponse(HttpContext context, Ban? ban, string error, BanZone zone) { context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/json"; await context.Response.WriteAsJsonAsync(new { error, reason = ban?.Reason, until = ban?.EndTime == DateTime.MaxValue ? null : ban?.EndTime, zone = zone.ToString(), banId = ban?.Id }); } } }