Files

35 lines
1.4 KiB
Go
Raw Permalink Normal View History

2026-09-18 12:03:35 +02:00
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
package web
import (
"encoding/json/v2"
"log/slog"
"net/http"
)
// CrossOrigin refuses a state-changing request that a browser sent from
// another origin, using the standard library's Fetch Metadata check:
// Sec-Fetch-Site when the browser sends it, and the Origin header against
// the Host header otherwise.
//
// It is the outer gate, and the admin's per-session CSRF token is the
// inner one, because the two cover different cases: this refuses a
// cross-site request before any handler runs, and the token also refuses
// a same-site request (another port on the same host) and a browser that
// sends neither header, which this check deliberately allows as a
// non-browser client.
func CrossOrigin() func(http.Handler) http.Handler {
protection := http.NewCrossOriginProtection()
protection.SetDenyHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Warn("web: refused a cross-origin request",
"method", r.Method, "path", r.URL.Path, "origin", r.Header.Get("Origin"))
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusForbidden)
_ = json.MarshalWrite(w, map[string]any{"error": "cross_origin"}, json.Deterministic(true))
}))
return protection.Handler
}