oficina / calendar Public

Preserve identical booking refusals with canonical themed 404

Co-Authored-By: Codex <noreply@openai.com>
Paul Campbell pushed by paul@keymail.dev cb87c75dd7454f6175d3c76a55234a525bb6ad77 parent b1be7ae
3 files changed, +38 −7
  • internal/calendar/handlers_booking_util.go +7 −3
  • internal/calendar/routes.go +1 −4
  • internal/calendartest/notfound_test.go +30 −0

CI passed — run details

diff --git a/internal/calendar/handlers_booking_util.go b/internal/calendar/handlers_booking_util.go
index 11ac92d..9e196ac 100644
--- a/internal/calendar/handlers_booking_util.go
+++ b/internal/calendar/handlers_booking_util.go
@@ -3,6 +3,7 @@ package calendar
import (
"net"
"net/http"
+ "net/url"
"strconv"
"strings"
"sync"
@@ -53,10 +54,13 @@ func publicHeaders(w http.ResponseWriter) {
}
// notFoundLikeTheRouter is the SAME 404 an unknown route gets, so a
-// wrong token and a wrong path are indistinguishable (B10). chi's
-// default NotFound is http.NotFound.
+// wrong token and a wrong path are indistinguishable (B10).
func notFoundLikeTheRouter(w http.ResponseWriter, r *http.Request) {
- http.NotFound(w, r)
+ page := r.Clone(r.Context())
+ page.URL = &url.URL{Path: "/"}
+ page.RequestURI = "/"
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ ErrorPage(w, page, http.StatusNotFound, "")
}
// zoneChoices is CommonZones plus the host's own zone when it is not
diff --git a/internal/calendar/routes.go b/internal/calendar/routes.go
index 2c60129..3095c7c 100644
--- a/internal/calendar/routes.go
+++ b/internal/calendar/routes.go
@@ -26,10 +26,7 @@ func (a *App) routes(h *handlers, authPlugin *rauth.Auth, list allowlist, origin
// covered. Origin-checking, not tokens — nothing to mint or forget
// in a form.
r.Use(csrf.Protect(origin))
- r.NotFound(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- ErrorPage(w, r, http.StatusNotFound, "")
- })
+ r.NotFound(notFoundLikeTheRouter)
// Signed-out.
r.Get("/signin", h.signin)
diff --git a/internal/calendartest/notfound_test.go b/internal/calendartest/notfound_test.go
index ca17d51..82fa574 100644
--- a/internal/calendartest/notfound_test.go
+++ b/internal/calendartest/notfound_test.go
@@ -51,3 +51,33 @@ func TestNotFoundLeavesMatchedRoutesAlone(t *testing.T) {
}
}
}
+
+func TestThemedNotFoundKeepsBookingFailuresIdentical(t *testing.T) {
+ h := newApp(t).Handler()
+ ordinary := get(t, h, "/unknown-path-sentinel?query-sentinel=1")
+ for _, path := range []string{"/book/unknown-host-sentinel/unknown-code-sentinel?query-sentinel=1", "/book/m/wrong-token-sentinel?query-sentinel=1"} {
+ r := get(t, h, path)
+ if r.Code != http.StatusNotFound || r.Body.String() != ordinary.Body.String() {
+ t.Errorf("booking refusal differs from ordinary404: status %d", r.Code)
+ }
+ if r.Header().Get("Cache-Control") != "no-store" || r.Header().Get("Referrer-Policy") != "no-referrer" {
+ t.Error("public capability headers changed")
+ }
+ if r.Header().Get("Content-Type") != "text/html; charset=utf-8" {
+ t.Error("booking refusal lacks HTML type")
+ }
+ }
+ if strings.Contains(ordinary.Body.String(), "sentinel") {
+ t.Error("request coordinate reflected")
+ }
+ if !strings.Contains(ordinary.Body.String(), `name="return_to" value="/"`) {
+ t.Error("mode return is not canonical")
+ }
+ req := httptest.NewRequest(http.MethodGet, "/unknown-path-sentinel?query-sentinel=1", nil)
+ req.URL.RawPath = "/unknown%2Dpath-sentinel"
+ originalURL, originalURI := *req.URL, req.RequestURI
+ h.ServeHTTP(httptest.NewRecorder(), req)
+ if *req.URL != originalURL || req.RequestURI != originalURI {
+ t.Error("render mutated caller request")
+ }
+}