using BoardApp.Session; namespace BoardApp.Middleware; public sealed class BoardRequireAuthMiddleware { private readonly RequestDelegate _next; public BoardRequireAuthMiddleware(RequestDelegate next) { _next = next; } private static bool IsAnonymousAllowed(PathString path) { // PathBase (/board) is stripped by UsePathBase, so we only see inner path here. var p = path.Value ?? ""; if (p.StartsWith("/static/", StringComparison.OrdinalIgnoreCase)) return true; if (string.Equals(p, "/login", StringComparison.OrdinalIgnoreCase)) return true; if (string.Equals(p, "/api/login", StringComparison.OrdinalIgnoreCase)) return true; if (string.Equals(p, "/api/winauth/login", StringComparison.OrdinalIgnoreCase)) return true; if (string.Equals(p, "/api/winauth/negotiate", StringComparison.OrdinalIgnoreCase)) return true; if (string.Equals(p, "/check/", StringComparison.OrdinalIgnoreCase)) return true; // Everything else under /board requires login (same as Node version router-level auth). return false; } public async Task Invoke(HttpContext ctx) { if (IsAnonymousAllowed(ctx.Request.Path)) { await _next(ctx); return; } var user = ctx.Session.GetJson("user"); if (user is not null && !string.IsNullOrWhiteSpace(user.Username)) { await _next(ctx); return; } var nextUrl = (ctx.Request.PathBase + ctx.Request.Path + ctx.Request.QueryString).ToString(); var acceptsHtml = ctx.Request.Headers.Accept.Any(a => a?.Contains("text/html", StringComparison.OrdinalIgnoreCase) == true); if (acceptsHtml) { var redirect = (ctx.Request.PathBase + "/login?next=" + Uri.EscapeDataString(nextUrl)).ToString(); ctx.Response.Redirect(redirect); return; } ctx.Response.StatusCode = StatusCodes.Status401Unauthorized; await ctx.Response.WriteAsJsonAsync(new { error = "Unauthorized" }); } }