From d3a7611f2a3eda597d363b963cb2e73f45c88a07 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 11 Oct 2026 00:15:13 +0200 Subject: [PATCH] Vote page: explicit no-store (the antiforgery write no longer supplies it), handler must match the request, empty Origin counts as present Co-Authored-By: Claude Opus 5.5 --- .../Features/ConceptVotes/CastRequestGuard.cs | 2 +- src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Bifrost.Web/Features/ConceptVotes/CastRequestGuard.cs b/src/Bifrost.Web/Features/ConceptVotes/CastRequestGuard.cs index 6af4a6b..ffd1b47 100644 --- a/src/Bifrost.Web/Features/ConceptVotes/CastRequestGuard.cs +++ b/src/Bifrost.Web/Features/ConceptVotes/CastRequestGuard.cs @@ -18,7 +18,7 @@ public static class CastRequestGuard } var origin = request.Headers.Origin; - if (origin.Count > 0) + if (request.Headers.ContainsKey("Origin")) { return origin.Count == 1 && string.Equals(origin[0], site.GetLeftPart(UriPartial.Authority), StringComparison.Ordinal); } diff --git a/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs b/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs index 5912225..525811d 100644 --- a/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs +++ b/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs @@ -12,6 +12,7 @@ namespace Bifrost.Web.Pages.Mill; // The anonymous vote page. Counts and the visitor's own stance come from ConceptAppraisalService on every GET; the // voter key is decided by VoterIdentity.For from the actor and the device id VoterKeyResolver reads. [IgnoreAntiforgeryToken] +[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] public sealed class VoteModel( ICurrentBifrostActorAccessor actorAccessor, ConceptAppraisalService appraisals, @@ -40,6 +41,14 @@ public sealed class VoteModel( return; } + // The handler the request names must be the handler selected: GET ?handler=Up has no OnGetUp to run. + var named = context.HttpContext.Request.Query["handler"].ToString(); + if (string.IsNullOrEmpty(named) != string.IsNullOrEmpty(context.HandlerMethod.Name)) + { + context.Result = new StatusCodeResult(StatusCodes.Status400BadRequest); + return; + } + if (HttpMethods.IsPost(method) && !CastRequestGuard.Allows(context.HttpContext.Request, host.Value)) { context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);