Files
Kontrollverlust/middleware.go
T
2026-09-03 22:20:55 +02:00

88 lines
2.7 KiB
Go

package main
import (
"context"
"log"
"net"
"net/http"
"os"
)
type ctxKey string
const uidKey ctxKey = "uid"
// torNet ist das interne Netz, über das TOR-Anfragen beim Backend ankommen:
// der Onion-Hidden-Service reicht über einen lokalen Proxy weiter, eine echte
// Client-IP gibt es dabei nicht -> die Anfragen erscheinen mit einer IP aus
// diesem Netz (siehe Impression-Log: 10.89.0.0). Beiträge von dort werden
// vorerst komplett abgewiesen; eine Moderations-/Freigabeseite kommt später.
// Per KVER_TOR_NET (CIDR) konfigurierbar; leer schaltet die Sperre ab.
var torNet *net.IPNet
func init() {
// LookupEnv statt envOr: envOr würde bei leer GESETZTER Variable den
// Default liefern -- dann ließe sich die Sperre nie abschalten. Nur eine
// UNgesetzte Variable fällt auf den Default zurück; KVER_TOR_NET= (leer)
// deaktiviert die Sperre.
cidr, ok := os.LookupEnv("KVER_TOR_NET")
if !ok {
cidr = "10.89.0.0/16"
}
if cidr == "" {
return
}
if _, n, err := net.ParseCIDR(cidr); err == nil {
torNet = n
} else {
log.Printf("KVER_TOR_NET ignoriert (ungültiges CIDR %q): %v", cidr, err)
}
}
// blockTOR weist Anfragen aus dem TOR-Netz ab (siehe torNet). Die volle IP wird
// hier nur für diese Zugriffsentscheidung geprüft, nicht gespeichert oder
// geloggt. Ist kein Netz konfiguriert, ist die Middleware ein No-op.
var blockTOR = guard(func(_ http.ResponseWriter, r *http.Request) error {
if isTOR(r) {
return errTORBlocked
}
return nil
})
var errTORBlocked = Forbidden("tor.blocked", "Beiträge über TOR sind derzeit nicht möglich.")
// isTOR erkennt Anfragen aus dem TOR-Netz. Ausgelagert, damit die
// serverseitig gerenderten Seiten das Beitragsformular erst gar nicht anbieten,
// statt es anzuzeigen und beim Absenden abzulehnen.
func isTOR(r *http.Request) bool {
if torNet == nil {
return false
}
ip := clientIP(r.RemoteAddr)
return ip != nil && torNet.Contains(ip)
}
var errAuthRequired = Unauth("auth.required", "Nicht angemeldet")
// requireAuth lehnt Anfragen ohne gültige Session ab und legt die uid in den
// Context. Nicht über guard, weil hier nicht nur geprüft, sondern auch der
// Request angereichert wird.
func requireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s, ok := getSession(r)
if !ok {
// Über failRequest: ohne JavaScript ist die Session-Abfrage sonst
// eine JSON-Zeile mitten im Browser.
failRequest(w, r, errAuthRequired)
return
}
ctx := context.WithValue(r.Context(), uidKey, s.UID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func uidFromContext(ctx context.Context) int64 {
uid, _ := ctx.Value(uidKey).(int64)
return uid
}