- Moderationsseite und Melde-Funktion (report) komplett ausgebaut; folgt als eigenständiges Projekt - alle API-Endpunkte unter /api, dort ausschließlich JSON: auch 404, 405, Rate-Limit (429) und Panics (500) - Fehler nur über HTTP-Status; stille DB-Fehler in stats, logout und Vote-Zählern liefern jetzt 500 statt Nullen - /auth/headerbar entfernt (Frontend nutzt /api/user/info) - Frontend auf /api und statusbasierte Auswertung umgestellt - notes/api.md neu als Referenz der aktuellen API Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
84 lines
2.3 KiB
Go
84 lines
2.3 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.
|
|
// 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.
|
|
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 {
|
|
writeAPIError(w, 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
|
|
}
|