package ratelimit import ( "fmt" "math" "net/http" "gitea.perlbach24.de/scripte/nexarch/internal/apiserver" ) // KeyFunc bestimmt den Rate-Limit-Schluessel fuer eine Anfrage — typischerweise // der Tenant-Slug aus apiserver.RequestContext, siehe KeyByTenant. Als // eigenstaendiger Typ austauschbar (z.B. spaeter API-Token-basiert), ohne // internal/apiserver aendern zu muessen (Kein Umbau angrenzender Bereiche). type KeyFunc func(r *http.Request) (string, bool) // KeyByTenant liest den Tenant aus dem von internal/apiserver.authAndTenantContext // gesetzten RequestContext (IAM-02/API-01) — dieselbe Middleware-Kette, // keine zweite Authentifizierung. func KeyByTenant(r *http.Request) (string, bool) { rc, ok := apiserver.FromContext(r.Context()) if !ok || rc.TenantSlug == "" { return "", false } return rc.TenantSlug, true } // Middleware setzt sich VOR den eigentlichen Handler (nach Auth+Tenant- // Kontext, siehe KeyByTenant) und lehnt Anfragen ueber dem konfigurierten // Limit mit 429 + Retry-After ab (Akzeptanzkriterium 3). func Middleware(store *Store, keyFn KeyFunc, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { key, ok := keyFn(r) if !ok { // Kein Schluessel ermittelbar (z.B. kein Tenant-Kontext) — die // Auth-Pruefung selbst ist Sache von authAndTenantContext, hier // wird nur nicht limitiert, wenn schon kein Kontext vorliegt. next(w, r) return } result, err := store.Allow(r.Context(), key) if err != nil { apiserver.WriteError(w, http.StatusInternalServerError, "rate_limit_error", "rate-limit-pruefung fehlgeschlagen") return } w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", result.Limit)) w.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", result.Remaining)) if !result.Allowed { retryAfterSeconds := int(math.Ceil(result.RetryAfter.Seconds())) if retryAfterSeconds < 1 { retryAfterSeconds = 1 } w.Header().Set("Retry-After", fmt.Sprintf("%d", retryAfterSeconds)) apiserver.WriteError(w, http.StatusTooManyRequests, "rate_limit_exceeded", "zu viele anfragen, bitte spaeter erneut versuchen") return } next(w, r) } }