Files
petrbalvin f8ed33df83
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Initial commit
Assisted-by: GLM 5.3
2026-09-29 10:03:32 +02:00

35 lines
1.4 KiB
Go

// 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
}