Verwaltung: Zugangscode wieder bei jedem Oeffnen noetig

Dogi hat die Lockerung vom 04.08.2026 ("Code nur einmal am Tag") heute
zurueckgenommen. Gewaehlte Auspraegung: Code beim OEFFNEN der Seite/App,
Neuladen im selben Tab wirft nicht raus, keine Abmeldung bei Untaetigkeit.

- admin-auth.js legt die Sitzung jetzt in sessionStorage statt in
  localStorage: gehoert zum Tab/App-Fenster, uebersteht F5 und Uploads,
  ist beim naechsten Oeffnen weg. Zweiter Tab = eigener Code.
- Alte localStorage-Sitzung wird beim ersten Laden einmalig entfernt --
  sonst waere Dogi trotz Umstellung mit dem alten Token weiter drin und
  ein Token laege monatelang im Browser herum.
- Versionsnummer des Skripts hochgesetzt: Caddy liefert Assets mit
  max-age=14400, der Browser haette die alte Anmelde-Logik sonst bis zu
  vier Stunden weiterbenutzt.
- Der 24-Std-Ablauf bleibt als Rueckfallsicherung fuer tagelang offene
  Tabs; der Server erzwingt dieselbe Grenze weiterhin selbst.
- Neuer Test pruef-verwaltung-anmeldung.mjs (12 Pruefungen: Gate beim
  Oeffnen, alte Sitzung wirkungslos, falscher/richtiger Code, F5 bleibt
  drin, neuer Tab verlangt Code, Abmelden raeumt auf, Untaetigkeit meldet
  NICHT ab).
- pruef-verwaltung-kacheln.mjs: echten Aufruf ans Live-Backend abgefangen
  und auf die Supporter-Zeilen gewartet -- der Test hatte dadurch
  gelegentlich falsch gemeldet.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-27 13:28:14 +02:00
co-authored by Claude Opus 5
parent 0465a7a14a
commit 7960b212c8
4 changed files with 226 additions and 20 deletions
+170
View File
@@ -0,0 +1,170 @@
/* =====================================================================
Verwaltung: Zugangscode bei JEDEM Öffnen (Nutzer-Wunsch 27.08.2026)
Geprüft wird die Regel, die Dogi ausgewählt hat:
- Code beim Öffnen der Seite/App
- Neuladen im selben Tab wirft NICHT raus
- jeder neue Tab / jeder App-Start verlangt wieder den Code
- keine Abmeldung bei Untätigkeit (ausdrücklich nicht gewünscht)
Zusätzlich: die alte, tagelang gültige Sitzung aus localStorage darf
nicht mehr greifen und wird beim ersten Laden entfernt — sonst wäre
Dogi nach dem Update weiterhin ohne Code drin.
Der Login-Endpunkt liegt auf postfach.dogfather-universe.com; er wird
hier abgefangen und beantwortet (inkl. CORS-Vorabfrage), damit kein
echter Zugangscode nötig ist.
===================================================================== */
import { chromium } from "playwright";
import http from "http";
import fs from "fs";
import path from "path";
const TYP = {
".html": "text/html", ".css": "text/css", ".js": "text/javascript",
".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp",
".json": "application/json", ".ico": "image/x-icon", ".jpg": "image/jpeg",
".webmanifest": "application/manifest+json",
};
const srv = http.createServer((q, r) => {
const p = path.join(process.cwd(), decodeURIComponent(q.url.split("?")[0]));
if (fs.existsSync(p) && fs.statSync(p).isFile()) {
r.writeHead(200, { "content-type": TYP[path.extname(p)] || "text/plain" });
return r.end(fs.readFileSync(p));
}
r.writeHead(404);
r.end("");
});
let ok = 0, fehl = 0;
const funde = [];
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); funde.push(name + " — " + info); }
};
const CORS = {
"access-control-allow-origin": "*",
"access-control-allow-headers": "content-type,authorization",
"access-control-allow-methods": "POST,OPTIONS",
};
const URL_SEITE = "http://127.0.0.1:4426/verwaltung.html";
const RICHTIGER_CODE = "GEHEIM-TEST";
/** Fängt alle Aufrufe ans Postfach-Backend ab. */
async function backendAttrappe(ziel) {
await ziel.route("https://postfach.dogfather-universe.com/**", async (route) => {
const req = route.request();
if (req.method() === "OPTIONS") return route.fulfill({ status: 204, headers: CORS, body: "" });
const pfad = new URL(req.url()).pathname;
const daten = (() => {
if (pfad === "/auth/login") {
const code = (req.postDataJSON() || {}).code;
return code === RICHTIGER_CODE
? { ok: true, token: "test-token", isOwner: true, name: "Dogi", permissions: [] }
: { ok: false, error: "Zugangscode falsch." };
}
if (pfad === "/admin/supporter/list") return { ok: true, supporters: [] };
if (pfad === "/admin/overview") return { ok: true, premiums: [], payments: [] };
if (pfad === "/admin/events/list") return { ok: true, events: [null, null] };
if (pfad === "/admin/polls/list") return { ok: true, polls: [] };
if (pfad === "/admin/team/list") return { ok: true, members: [] };
if (pfad === "/admin/testimonials/list") return { ok: true, testimonials: [] };
if (pfad === "/admin/special-event") return { ok: true, event: null };
return { ok: true };
})();
await route.fulfill({ status: 200, headers: { ...CORS, "content-type": "application/json" }, body: JSON.stringify(daten) });
});
}
const lage = (seite) => seite.evaluate(() => ({
gateSichtbar: getComputedStyle(document.getElementById("gate-container")).display !== "none",
verwaltungSichtbar: getComputedStyle(document.getElementById("supp-root")).display !== "none",
imTab: !!sessionStorage.getItem("dogfather_admin_session"),
imBrowser: !!localStorage.getItem("dogfather_admin_session"),
}));
(async () => {
await new Promise((r) => srv.listen(4426, "127.0.0.1", r));
const browser = await chromium.launch();
const kontext = await browser.newContext();
await backendAttrappe(kontext);
/* ---------- 1. Frisch geöffnet ---------- */
console.log("\n== Frisch geöffnet ==");
const tab = await kontext.newPage();
await tab.goto(URL_SEITE);
await tab.waitForTimeout(400);
let l = await lage(tab);
pruefe("Zugangswand wird verlangt", l.gateSichtbar && !l.verwaltungSichtbar, JSON.stringify(l));
/* ---------- 2. Alte Tages-Sitzung aus localStorage ---------- */
console.log("\n== Alte 24-Std-Sitzung aus localStorage ==");
await tab.evaluate(() => localStorage.setItem("dogfather_admin_session", JSON.stringify({
token: "alt-token", isOwner: true, name: "Dogi", permissions: [], loggedInAt: Date.now(),
})));
await tab.reload();
await tab.waitForTimeout(400);
l = await lage(tab);
pruefe("Alte Sitzung öffnet NICHT mehr", l.gateSichtbar && !l.verwaltungSichtbar, JSON.stringify(l));
pruefe("Alte Sitzung wird aus dem Browser entfernt", !l.imBrowser, "localStorage leer: " + !l.imBrowser);
/* ---------- 3. Falscher Code ---------- */
console.log("\n== Falscher Code ==");
await tab.fill("#code-input-dogi", "FALSCH");
await tab.press("#code-input-dogi", "Enter");
await tab.waitForTimeout(500);
l = await lage(tab);
pruefe("Falscher Code kommt nicht rein", l.gateSichtbar && !l.verwaltungSichtbar, JSON.stringify(l));
/* ---------- 4. Richtiger Code ---------- */
console.log("\n== Richtiger Code ==");
await tab.fill("#code-input-dogi", RICHTIGER_CODE);
await tab.press("#code-input-dogi", "Enter");
await tab.waitForTimeout(800);
l = await lage(tab);
pruefe("Verwaltung öffnet sich", !l.gateSichtbar && l.verwaltungSichtbar, JSON.stringify(l));
pruefe("Sitzung liegt im Tab (sessionStorage)", l.imTab, "sessionStorage gesetzt");
pruefe("Sitzung liegt NICHT dauerhaft im Browser", !l.imBrowser, "localStorage leer");
/* ---------- 5. Neuladen im selben Tab ---------- */
console.log("\n== Neuladen im selben Tab (F5) ==");
await tab.reload();
await tab.waitForTimeout(600);
l = await lage(tab);
pruefe("Neuladen wirft nicht raus", !l.gateSichtbar && l.verwaltungSichtbar, JSON.stringify(l));
/* ---------- 6. Neuer Tab ---------- */
console.log("\n== Neuer Tab / neuer App-Start ==");
const tab2 = await kontext.newPage();
await tab2.goto(URL_SEITE);
await tab2.waitForTimeout(400);
const l2 = await lage(tab2);
pruefe("Neuer Tab verlangt den Code", l2.gateSichtbar && !l2.verwaltungSichtbar, JSON.stringify(l2));
await tab2.close();
/* ---------- 7. Abmelden ---------- */
console.log("\n== Abmelden ==");
await tab.click("#logout-btn");
await tab.waitForTimeout(600);
l = await lage(tab);
pruefe("Abmelden zeigt wieder die Zugangswand", l.gateSichtbar && !l.verwaltungSichtbar, JSON.stringify(l));
pruefe("Abmelden räumt die Sitzung weg", !l.imTab && !l.imBrowser, "beide Speicher leer");
/* ---------- 8. Kein Rauswurf beim Herumliegen ---------- */
console.log("\n== Untätigkeit (soll NICHT abmelden) ==");
await tab.fill("#code-input-dogi", RICHTIGER_CODE);
await tab.press("#code-input-dogi", "Enter");
await tab.waitForTimeout(800);
await tab.waitForTimeout(5000); // 5 Sekunden unberührt liegen lassen
l = await lage(tab);
pruefe("Nach Untätigkeit weiterhin eingeloggt", !l.gateSichtbar && l.verwaltungSichtbar, JSON.stringify(l));
await browser.close();
srv.close();
console.log("\n===== " + ok + " ok, " + fehl + " Fehler =====");
if (funde.length) { console.log("\nOffene Punkte:"); funde.forEach((f) => console.log(" - " + f)); }
process.exit(fehl ? 1 : 0);
})();