# Anfragelimits (Rate Limiting)

Letzte Aktualisierung: 2026-02-21

Dieses Dokument beschreibt das aktuelle zentrale Rate-Limiting für Login und API.

## Ziel

- Brute-Force auf Login reduzieren.
- API-Missbrauch (Burst/Spam) abfangen.
- Einheitliches Verhalten mit `429` + `Retry-After`.

## Technischer Aufbau

### Storage

Tabelle: `request_rate_limits`

Spalten:
- `scope`: Technischer Bereich (z. B. `login.password.email_ip`)
- `subject_hash`: SHA-256 Hash des Subjects (keine Klartexte)
- `hits`: Treffer im aktuellen Fenster
- `window_started_at`: Beginn des aktuellen Zeitfensters (UTC)
- `blocked_until`: Block-Ende (UTC), `NULL` wenn nicht geblockt

Schema:
- Tabelle wird in `db/init/init.sql` mit angelegt.
- Für Bestandsumgebungen sollte ein idempotentes SQL-Update bereitgestellt werden.

### Service/Repository

- Service: `/lib/Service/Security/RateLimiterService.php`
- Repository: `/lib/Repository/Security/RateLimitRepository.php`

Der Service ist absichtlich **fail-open**:
- Wenn DB/Storage fehlschlägt, wird kein harter Lock erzeugt.
- Requests laufen weiter, um Verfügbarkeit nicht zu brechen.

## API Rate Limiting

Integration:
- `/lib/Http/ApiBootstrap.php` (vor Auth)

Aktuelle Limits:

1. Bearer vorhanden (`token + ip`)
- Scope: `api.token_ip`
- Key: `<selector>|<ip>`
- Limit: `300` Hits pro `60` Sekunden
- Block: `120` Sekunden

2. Kein Bearer (`ip-only`)
- Scope: `api.ip`
- Key: `<ip>`
- Limit: `120` Hits pro `60` Sekunden
- Block: `120` Sekunden

Bei überschreitung:
- Response: `429`
- Body: `{"error":"rate_limit_exceeded"}`
- Header: `Retry-After: <sekunden>`

## Login Rate Limiting

Integration:
- `/pages/auth/login().php`

Aktuelle Limits:

1. Schritt `resolve_email` (IP)
- Scope: `login.resolve.ip`
- Key: `<ip>`
- Limit: `25` Hits pro `600` Sekunden
- Block: `300` Sekunden

2. Schritt `login_password` (email + ip)
- Scope: `login.password.email_ip`
- Key: `<lowercase-email>|<ip>`
- Limit: `5` Fehlversuche pro `900` Sekunden
- Block: `900` Sekunden

Verhalten:
- Bei Block: HTTP `429` + `Retry-After`.
- UI zeigt generische Meldung: `Too many login attempts. Please wait and try again.`
- Bei erfolgreichem Passwort-Login wird der Passwort-Scope für diesen Key zurückgesetzt.

## Datenfluss pro Request

1. Key bilden (`scope` + Subject)
2. Subject hashen (`sha256`)
3. Bestehenden Datensatz lesen
4. Falls `blocked_until > now`: direkt blocken
5. Fenster prüfen (`window_started_at + windowSeconds`)
6. Hits erhöhen
7. Bei `hits > max`: `blocked_until = now + blockSeconds`
8. Ergebnis liefern (`allowed`, `retry_after`)

## Operative Hinweise

- Zeitbasis ist UTC (`gmdate`).
- Tabelle kann wachsen; in V1 gibt es noch keinen automatischen Cleanup für alte Rate-Limit-Zeilen.
- Bei Bedarf kann ein Wartungsjob alte/obsolete Zeilen periodisch löschen.

## Smoke-Test (manuell)

API:
1. Schnell >120 Requests/min ohne Bearer gegen `/api/v1/*`
2. Erwartung: `429` mit `Retry-After`

Login:
1. Mehrere falsche Passwortversuche für dieselbe Email+IP
2. Erwartung nach 5 Fehlversuchen: `429` und Sperre für 15 Minuten

## Erweiterungen (nächster Schritt)

- Limits in Settings pflegbar machen (statt Hardcode).
- Optional stricter Mode in Production (fail-closed nur für API).
- Cleanup-Job für `request_rate_limits`.
- Optional Logging/Stats für geblockte Requests (z. B. im Admin-Stats Modul).
