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]>
86 lines
3.6 KiB
JavaScript
86 lines
3.6 KiB
JavaScript
/* =====================================================================
|
|
test-kontingent-routen.mjs — die Anfragebremse an den echten Routen
|
|
|
|
Prüft nicht den Baustein selbst (das tut pruef-kontingent.mjs im
|
|
Projektstamm), sondern dass die beiden bisher ungebremsten Routen
|
|
submitApplication und submitTestimonial ihn WIRKLICH benutzen:
|
|
ab der Grenze kommt 429, und verschiedene IPs stören sich nicht.
|
|
|
|
Läuft gegen eine Wegwerf-Datenbank, ruft die Handler direkt mit
|
|
Attrappen auf -- gleiches Muster wie test-webdesign-anfragen.mjs.
|
|
|
|
Aufruf (aus server-internal/): node test-kontingent-routen.mjs
|
|
===================================================================== */
|
|
import { mkdtempSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
process.env.DB_PATH = join(mkdtempSync(join(tmpdir(), "kont-test-")), "test.db");
|
|
|
|
const { initDb } = await import("./db.js");
|
|
initDb();
|
|
|
|
const { submitApplication } = await import("./routes/applications.js");
|
|
const { submitTestimonial } = await import("./routes/testimonials.js");
|
|
|
|
let ok = 0, schlecht = 0;
|
|
const pruefe = (name, bedingung, zusatz = "") => {
|
|
if (bedingung) { ok++; console.log(" ok " + name); }
|
|
else { schlecht++; console.log(" FEHLT " + name + " " + zusatz); }
|
|
};
|
|
|
|
function antwortAttrappe() {
|
|
const a = { code: 200, daten: null };
|
|
a.status = (c) => { a.code = c; return a; };
|
|
a.json = (d) => { a.daten = d; return a; };
|
|
return a;
|
|
}
|
|
function req(koerper, ip) {
|
|
return { body: koerper, params: {}, query: {}, headers: {}, ip, socket: { remoteAddress: ip } };
|
|
}
|
|
|
|
/* Eine gültige Bewerbung bzw. Stimme, damit NICHT eine Inhaltsprüfung
|
|
(Pflichtfeld o.ä.) fälschlich die 429-Messung verfälscht. */
|
|
const bewerbung = () => ({ type: "creator", data: { name: "Test Person", warum: "Ich will dabei sein und mithelfen." } });
|
|
const stimme = () => ({ name: "Test Person", tiktokUsername: "testi", nachricht: "Das Team ist wirklich super, ich bin gern dabei!" });
|
|
|
|
async function feuere(handler, koerperFn, ip, anzahl) {
|
|
let durch = 0, gesperrt = 0;
|
|
for (let i = 0; i < anzahl; i++) {
|
|
const res = antwortAttrappe();
|
|
await handler(req(koerperFn(), ip), res);
|
|
if (res.code === 429) gesperrt++;
|
|
else durch++;
|
|
}
|
|
return { durch, gesperrt };
|
|
}
|
|
|
|
console.log("\n=== Bewerbungen: 20 pro Stunde, dann 429 ===");
|
|
{
|
|
const r = await feuere(submitApplication, bewerbung, "198.51.100.1", 25);
|
|
pruefe("genau 20 kommen durch, der Rest wird gebremst", r.durch === 20 && r.gesperrt === 5, `durch=${r.durch}, gesperrt=${r.gesperrt}`);
|
|
}
|
|
|
|
console.log("\n=== Stimmen: 20 pro Stunde, dann 429 ===");
|
|
{
|
|
const r = await feuere(submitTestimonial, stimme, "198.51.100.2", 25);
|
|
pruefe("genau 20 kommen durch, der Rest wird gebremst", r.durch === 20 && r.gesperrt === 5, `durch=${r.durch}, gesperrt=${r.gesperrt}`);
|
|
}
|
|
|
|
console.log("\n=== Getrennte Töpfe: erschöpfte Bewerbung sperrt keine Stimme ===");
|
|
{
|
|
await feuere(submitApplication, bewerbung, "198.51.100.3", 25); // Bewerbung erschöpfen
|
|
const r = await feuere(submitTestimonial, stimme, "198.51.100.3", 1); // Stimme derselben IP
|
|
pruefe("dieselbe IP darf noch eine Stimme abgeben", r.durch === 1 && r.gesperrt === 0);
|
|
}
|
|
|
|
console.log("\n=== Verschiedene IPs stören sich nicht ===");
|
|
{
|
|
await feuere(submitApplication, bewerbung, "198.51.100.4", 25); // eine IP erschöpfen
|
|
const r = await feuere(submitApplication, bewerbung, "198.51.100.5", 1); // andere IP
|
|
pruefe("eine frische IP kommt durch", r.durch === 1 && r.gesperrt === 0);
|
|
}
|
|
|
|
console.log(`\n===== ${ok} bestanden, ${schlecht} fehlgeschlagen =====`);
|
|
process.exitCode = schlecht ? 1 : 0;
|