Anfragebremse für /submit und /testimonials/submit (die letzten zwei ungebremsten Routen)
Bei der Bestandsaufnahme als "keine Route hat eine Bremse" gemeldet -- das war falsch (grep suchte nach rateLimit/bremse, im Code heißen sie Kontingent/Sperre/Fehlversuche). Fast alle öffentlichen Routen SIND gebremst: Anmeldung, Supporter-Login, Upload, Kundenanfragen. Übrig blieben genau zwei schreibende Routen: Bewerbungen und Stimmen. Ohne Bremse könnte ein Skript die Datenbank mit Müll fluten. Kein Sicherheitsleck (beide landen in einer Warteschlange, nichts wird ungesehen veröffentlicht), aber eine sinnvolle Härtung. Statt das vorhandene Muster ein drittes Mal zu kopieren: ein Baustein lib/kontingent.js, den nun alle drei Routen nutzen. Zwei Verbesserungen gegenüber dem Original in testimonials.js: - getrennte Töpfe je Zweck (ein Bild-Upload verbraucht kein Bewerbungs-Kontingent) - Selbstreinigung: die alte Zähler-Map ließ jede IP für immer im Speicher stehen (langsames Leck), die neue räumt abgelaufene Einträge auf Grenzen: Uploads 10/Stunde/IP (belegen Plattenplatz), Text-Einreichungen 20/Stunde/IP (großzügig für geteilte Anschlüsse, stoppt Fluten). Tests: pruef-kontingent.mjs (Baustein, 7/7), test-kontingent-routen.mjs (echte Routen liefern 429 ab Grenze, getrennte Töpfe, IPs unabhängig, 4/4). Bestehende Tests unverändert grün (37/37). NOCH NICHT LIVE: server-internal läuft aus /home/dogiintern (kein Zugriff), wird mit den übrigen Server-Änderungen in einem Deploy live geschaltet. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -11,9 +11,16 @@ import { logAction } from "../lib/audit.js";
|
||||
import { json } from "../lib/http.js";
|
||||
import { bewerbungAlsTicketAnlegen } from "../lib/ticketanizer.js";
|
||||
import { bewerbungAlsDiscordNachrichtPosten } from "../lib/discord-webhooks.js";
|
||||
import { kontingentFrei } from "../lib/kontingent.js";
|
||||
|
||||
const ALLOWED_TYPES = ["creator", "scout", "kooperation", "modi"];
|
||||
|
||||
/* Öffentliche Route, keine Anmeldung: ohne Bremse könnte ein Skript die
|
||||
Bewerbungstabelle mit Müll fluten. 20/Stunde/IP ist großzügig genug
|
||||
für mehrere echte Bewerbungen hinter einem geteilten Anschluss
|
||||
(Familie, Mobilfunk-NAT) und stoppt trotzdem eine maschinelle Flut. */
|
||||
const BEWERBUNG_MAX_PRO_STUNDE = 20;
|
||||
|
||||
function can(session, key) {
|
||||
if (!session) return false;
|
||||
if (session.isOwner) return true;
|
||||
@@ -29,6 +36,10 @@ function viewPermissionForType(type) {
|
||||
}
|
||||
|
||||
export async function submitApplication(req, res) {
|
||||
const ip = req.ip || req.socket?.remoteAddress || "unbekannt";
|
||||
if (!kontingentFrei("bewerbung", ip, { maxProFenster: BEWERBUNG_MAX_PRO_STUNDE })) {
|
||||
return json(res, { ok: false, error: "Zu viele Bewerbungen in kurzer Zeit. Bitte versuch es später noch einmal." }, 429);
|
||||
}
|
||||
const body = req.body;
|
||||
const { type, data } = body || {};
|
||||
if (!ALLOWED_TYPES.includes(type) || typeof data !== "object" || !data) {
|
||||
|
||||
Reference in New Issue
Block a user