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:
@@ -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);
|
||||
})();
|
||||
Reference in New Issue
Block a user