Nutzer-Report 19.08.2026: "ich komme auf der .com nicht rein" -- der RICHTIGE Zugangscode wurde mit "Falscher Zugangscode" abgewiesen. Ursache: gateClientKey() nutzte req.ip. Das ist hier NICHT die IP des Besuchers, sondern die des Cloudflare-Knotens (Kette Besucher -> Cloudflare -> Caddy -> Express; bei trust proxy: 1 bleibt genau Cloudflare uebrig). Damit teilten sich alle Besucher EINEN Fehlversuchs-Zaehler -- fuenf Vertipper von irgendwem sperrten die Seite fuer jeden, 15 Minuten lang. Live nachgewiesen, nicht vermutet: derselbe richtige Code wurde ueber dogfather-universe.com abgelehnt und im selben Moment ueber www.dogfather-universe.com akzeptiert -- zwei Namen, zwei Cloudflare-Knoten, zwei getrennte Zaehler. Fix 1 -- echte Besucher-IP aus CF-Connecting-IP. Dieser Header war am 05.08.2026 bewusst verworfen worden, weil er faelschbar war, solange der Server auch direkt unter seiner IP erreichbar war. Diese Voraussetzung gilt nicht mehr: die Firewall laesst 80/443 nur noch aus den Cloudflare-Netzen zu. Vor der Umstellung von aussen gegengeprueft -- Direktzugriff auf beide Ports kommt gar nicht mehr zustande, der Header kann also nur von Cloudflare stammen. Abhaengigkeit im Code vermerkt: wird der Direktzugriff je wieder geoeffnet, muss diese Stelle zurueckgebaut werden. Ungueltige Header-Werte fallen sauber auf req.ip zurueck. Fix 2 -- ehrliche Meldung bei Sperre (429 statt 401), mit Restzeit in Minuten. Die bisher absichtlich identische Meldung sollte Angreifern nichts verraten, hat aber in der Praxis den Besitzer der Seite selbst ratlos gemacht: richtiger Code, Anzeige "Falscher Zugangscode", keine Chance zu erkennen dass nur eine Wartezeit laeuft. Die Sperre bleibt in voller Laenge bestehen, der Code wird dadurch nicht leichter erratbar. Fix 3 -- abgelaufene Eintraege werden aufgeraeumt. Pro echter Besucher-IP kann die Map sonst unbegrenzt wachsen (vorher gab es nur eine Handvoll Cloudflare-Knoten). Regressionstest ergaenzt (server/test-gate.mjs, 12 Pruefungen). Gegen den ALTEN Code laufen gezielt 5 davon auf Fehler -- darunter "Dogi kommt trotz fremder Sperre rein" --, gegen den neuen alle gruen. Der Test faengt also wirklich diesen Bug. Co-Authored-By: Claude Opus 5 <[email protected]>
86 lines
3.5 KiB
JavaScript
86 lines
3.5 KiB
JavaScript
/* Test der Zugangs-Sperre (19.08.2026).
|
|
Stellt den echten Fehlerfall nach: Frueher teilten sich ALLE Besucher einen einzigen
|
|
Fehlversuchs-Zaehler, weil req.ip die Cloudflare-IP war statt der des Besuchers.
|
|
Ohne echten Server -- gateAuthHandler wird direkt mit nachgebauten req/res aufgerufen. */
|
|
|
|
process.env.SITE_ACCESS_SECRET = "test-geheimnis-nur-fuer-diesen-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "RichtigerCode";
|
|
process.env.SITE_ACCESS_CODE_VANVAN = "VanVanCode";
|
|
|
|
const { gateAuthHandler } = await import("./gate.js");
|
|
|
|
function anfrage(code, besucherIp) {
|
|
return {
|
|
headers: { "cf-connecting-ip": besucherIp },
|
|
ip: "172.71.0.1", // immer DIESELBE Cloudflare-IP - genau das war der Ausloeser
|
|
body: { code },
|
|
};
|
|
}
|
|
|
|
function antwort() {
|
|
const r = {
|
|
statusCode: 200,
|
|
daten: null,
|
|
cookies: {},
|
|
status(c) { r.statusCode = c; return r; },
|
|
json(d) { r.daten = d; return r; },
|
|
cookie(n, w) { r.cookies[n] = w; return r; },
|
|
};
|
|
return r;
|
|
}
|
|
|
|
async function versuch(code, ip) {
|
|
const res = antwort();
|
|
await gateAuthHandler(anfrage(code, ip), res);
|
|
return res;
|
|
}
|
|
|
|
let fehler = 0;
|
|
function pruefe(bedingung, text) {
|
|
console.log(` ${bedingung ? "OK " : "FEHL"} ${text}`);
|
|
if (!bedingung) fehler++;
|
|
}
|
|
|
|
console.log("=== 1. Richtiger Code wird angenommen ===");
|
|
let r = await versuch("RichtigerCode", "5.5.5.5");
|
|
pruefe(r.daten?.ok === true, "richtiger Code -> ok");
|
|
pruefe(!!r.cookies.dogi_session, "Sitzungs-Cookie wird gesetzt");
|
|
pruefe(r.cookies.dogi_role === "dogi", "Rolle 'dogi' erkannt");
|
|
|
|
console.log("\n=== 2. VanVan kommt ebenfalls rein ===");
|
|
r = await versuch("VanVanCode", "5.5.5.6");
|
|
pruefe(r.daten?.ok === true && r.cookies.dogi_role === "vanvan", "VanVan-Code -> ok, Rolle 'vanvan'");
|
|
|
|
console.log("\n=== 3. Falscher Code wird abgewiesen ===");
|
|
r = await versuch("Quatsch", "5.5.5.7");
|
|
pruefe(r.daten?.ok === false && r.statusCode === 401, "falscher Code -> 401");
|
|
|
|
console.log("\n=== 4. Sperre nach 5 Fehlversuchen (Angreifer-IP 9.9.9.9) ===");
|
|
for (let i = 0; i < 5; i++) await versuch("falsch" + i, "9.9.9.9");
|
|
r = await versuch("nochmal", "9.9.9.9");
|
|
pruefe(r.statusCode === 429, "gesperrte IP -> 429 (statt irrefuehrend 401)");
|
|
pruefe(/Zu viele Fehlversuche/.test(r.daten?.error || ""), "ehrliche Meldung statt 'Falscher Zugangscode'");
|
|
pruefe(/Minute/.test(r.daten?.error || ""), "Restzeit wird genannt");
|
|
console.log(` Meldung: "${r.daten?.error}"`);
|
|
|
|
console.log("\n=== 5. DER EIGENTLICHE BUG: sperrt das auch Dogi aus? ===");
|
|
r = await versuch("RichtigerCode", "5.5.5.5");
|
|
pruefe(r.daten?.ok === true, "Dogi kommt trotz fremder Sperre rein (frueher: NEIN)");
|
|
|
|
console.log("\n=== 6. Auch der RICHTIGE Code bleibt fuer die gesperrte IP gesperrt ===");
|
|
r = await versuch("RichtigerCode", "9.9.9.9");
|
|
pruefe(r.statusCode === 429, "gesperrte IP kommt auch mit richtigem Code nicht rein");
|
|
|
|
console.log("\n=== 7. Ohne Cloudflare-Header: Rueckfall auf req.ip ===");
|
|
const res7 = antwort();
|
|
await gateAuthHandler({ headers: {}, ip: "10.0.0.5", body: { code: "RichtigerCode" } }, res7);
|
|
pruefe(res7.daten?.ok === true, "funktioniert auch ohne Cloudflare-Header");
|
|
|
|
console.log("\n=== 8. Gefaelschter Header wird nicht blind uebernommen ===");
|
|
const res8 = antwort();
|
|
await gateAuthHandler({ headers: { "cf-connecting-ip": "kein-ip-wert; DROP TABLE" }, ip: "10.0.0.6", body: { code: "RichtigerCode" } }, res8);
|
|
pruefe(res8.daten?.ok === true, "unsinniger Header -> sauberer Rueckfall, kein Absturz");
|
|
|
|
console.log(fehler === 0 ? "\n=> ALLE TESTS BESTANDEN" : `\n=> ${fehler} TESTS FEHLGESCHLAGEN`);
|
|
process.exit(fehler === 0 ? 0 : 1);
|