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:
+30
-12
@@ -3,14 +3,26 @@
|
|||||||
(dasselbe Owner-/Rollen-System wie schon fürs frühere Bewerbungs-
|
(dasselbe Owner-/Rollen-System wie schon fürs frühere Bewerbungs-
|
||||||
Postfach).
|
Postfach).
|
||||||
|
|
||||||
Session (Token + Rolle/Rechte) liegt in localStorage (Nutzer-Wunsch
|
WO DIE SITZUNG LIEGT — geändert am 27.08.2026
|
||||||
04.08.2026: "den Zugangscode auf der Verwaltungsseite nur einmal am Tag
|
|
||||||
eingeben müssen") — localStorage übersteht anders als sessionStorage
|
Bis dahin lag die Sitzung (Token + Rolle/Rechte) in localStorage, weil
|
||||||
auch das Schließen des Tabs/Browsers, sonst wäre "einmal am Tag" nicht
|
der Zugangscode auf Wunsch vom 04.08.2026 nur "einmal am Tag" nötig sein
|
||||||
möglich. Eigener 24-Stunden-Ablauf hier im Client (siehe SESSION_MAX_AGE_MS)
|
sollte. Dogi hat das am 27.08.2026 bewusst wieder zurückgenommen ("jedes
|
||||||
zusätzlich zum server-seitigen Session-Timeout (lib/auth.js, ebenfalls
|
mal"), und zwar in der Ausprägung: Code beim ÖFFNEN der Seite/App —
|
||||||
24 Std) — beide zusammen sorgen dafür, dass wirklich niemand länger als
|
Neuladen im selben Tab soll nicht rauswerfen, eine Untätigkeits-
|
||||||
einen Tag mit einem alten Code eingeloggt bleiben kann.
|
Abmeldung will er ausdrücklich nicht.
|
||||||
|
|
||||||
|
Genau das leistet sessionStorage: es gehört zum einzelnen Tab bzw. zum
|
||||||
|
Fenster der installierten App, übersteht ein Neuladen (F5, Formular-
|
||||||
|
Absenden, Bild-Upload), wird aber beim Schließen des Tabs/der App vom
|
||||||
|
Browser selbst geleert. Ein zweiter Tab startet mit leerem Speicher und
|
||||||
|
verlangt deshalb ebenfalls den Code. localStorage könnte das nicht — es
|
||||||
|
überlebt absichtlich das Schließen des Browsers.
|
||||||
|
|
||||||
|
Der 24-Stunden-Ablauf (SESSION_MAX_AGE_MS) bleibt als Rückfallsicherung
|
||||||
|
für den Fall, dass ein Tab tagelang offen liegen bleibt; der Server
|
||||||
|
erzwingt dieselbe Grenze noch einmal eigenständig (server-internal/
|
||||||
|
lib/auth.js). Sicherheit hängt also nicht am Browser allein.
|
||||||
|
|
||||||
Ursprünglich für postfach.html/zugaenge.html gebaut (Bewerbungs-
|
Ursprünglich für postfach.html/zugaenge.html gebaut (Bewerbungs-
|
||||||
Verwaltung) — die wurden am 01.08.2026 auf Dogis Wunsch entfernt ("nicht
|
Verwaltung) — die wurden am 01.08.2026 auf Dogis Wunsch entfernt ("nicht
|
||||||
@@ -24,12 +36,18 @@
|
|||||||
const SESSION_KEY = "dogfather_admin_session";
|
const SESSION_KEY = "dogfather_admin_session";
|
||||||
const SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
const SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
/* Altlast aufräumen: Wer vor dem 27.08.2026 eingeloggt war, hat noch eine
|
||||||
|
Sitzung samt Token in localStorage liegen. Die wird jetzt nicht mehr
|
||||||
|
gelesen — sie soll aber auch nicht monatelang im Browser herumliegen,
|
||||||
|
deshalb wird sie beim ersten Laden dieser Datei einmalig entfernt. */
|
||||||
|
try { localStorage.removeItem(SESSION_KEY); } catch { /* z.B. Speicher gesperrt */ }
|
||||||
|
|
||||||
function getSession() {
|
function getSession() {
|
||||||
try {
|
try {
|
||||||
const session = JSON.parse(localStorage.getItem(SESSION_KEY) || "null");
|
const session = JSON.parse(sessionStorage.getItem(SESSION_KEY) || "null");
|
||||||
if (!session) return null;
|
if (!session) return null;
|
||||||
if (!session.loggedInAt || Date.now() - session.loggedInAt > SESSION_MAX_AGE_MS) {
|
if (!session.loggedInAt || Date.now() - session.loggedInAt > SESSION_MAX_AGE_MS) {
|
||||||
clearSession(); // älter als 24 Std -> als abgelaufen behandeln, neuer Code nötig
|
clearSession(); // Rückfallsicherung: Tag-alter Tab -> neuer Code nötig
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return session;
|
return session;
|
||||||
@@ -39,11 +57,11 @@ function getSession() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSession(data) {
|
function setSession(data) {
|
||||||
localStorage.setItem(SESSION_KEY, JSON.stringify({ ...data, loggedInAt: Date.now() }));
|
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ ...data, loggedInAt: Date.now() }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSession() {
|
function clearSession() {
|
||||||
localStorage.removeItem(SESSION_KEY);
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Prüft, ob die aktuelle Session (Owner oder Rolle+Overrides) ein Recht hat. */
|
/** Prüft, ob die aktuelle Session (Owner oder Rolle+Overrides) ein Recht hat. */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
})();
|
||||||
@@ -123,9 +123,22 @@ async function laden(browser, viewport, dateiname) {
|
|||||||
await seite.route("**/assets/js/admin-auth.js*", (route) =>
|
await seite.route("**/assets/js/admin-auth.js*", (route) =>
|
||||||
route.fulfill({ status: 200, contentType: "text/javascript", body: ATTRAPPE })
|
route.fulfill({ status: 200, contentType: "text/javascript", body: ATTRAPPE })
|
||||||
);
|
);
|
||||||
|
/* Die Seite holt die Website-Texte mit einem eigenen fetch() direkt vom
|
||||||
|
Live-Backend — das ginge im Test wirklich ins Netz und verzögerte je
|
||||||
|
nach Laufzeit alles, was danach kommt (die Supporter-Liste wird erst
|
||||||
|
danach gefüllt). Deshalb hier abfangen: der Test soll messen, was die
|
||||||
|
Seite tut, nicht wie schnell der Server gerade antwortet. */
|
||||||
|
await seite.route("https://postfach.dogfather-universe.com/**", (route) =>
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
headers: { "access-control-allow-origin": "*", "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ ok: true, texte: {} }),
|
||||||
|
})
|
||||||
|
);
|
||||||
seite.on("pageerror", (e) => pruefe("Kein JS-Fehler (" + dateiname + ")", false, (e.stack || e.message).split("\n").slice(0, 4).join(" | ")));
|
seite.on("pageerror", (e) => pruefe("Kein JS-Fehler (" + dateiname + ")", false, (e.stack || e.message).split("\n").slice(0, 4).join(" | ")));
|
||||||
await seite.goto("http://127.0.0.1:4422/verwaltung.html");
|
await seite.goto("http://127.0.0.1:4422/verwaltung.html");
|
||||||
await seite.waitForSelector("[data-testimonial-id]", { timeout: 8000 });
|
await seite.waitForSelector("[data-testimonial-id]", { timeout: 8000 });
|
||||||
|
await seite.waitForSelector("#supporter-tbody tr[data-id]", { timeout: 8000 });
|
||||||
await seite.waitForTimeout(400);
|
await seite.waitForTimeout(400);
|
||||||
return seite;
|
return seite;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-8
@@ -1289,7 +1289,11 @@
|
|||||||
<script src="assets/js/data-manager.js?v=20260828b"></script>
|
<script src="assets/js/data-manager.js?v=20260828b"></script>
|
||||||
<script src="assets/js/data-stimmen-avatare.js?v=20260828b"></script>
|
<script src="assets/js/data-stimmen-avatare.js?v=20260828b"></script>
|
||||||
<script src="assets/js/main.js?v=20260828b"></script>
|
<script src="assets/js/main.js?v=20260828b"></script>
|
||||||
<script src="assets/js/admin-auth.js?v=20260828b"></script>
|
<!-- Versionsnummer bewusst hochgesetzt (27.08.2026): Caddy liefert Assets mit
|
||||||
|
max-age=14400, ein Browser hätte die alte Anmelde-Logik sonst bis zu vier
|
||||||
|
Stunden weiterbenutzt und Dogi wäre trotz Umstellung weiter ohne Code
|
||||||
|
drin gewesen. Neue Adresse = sofort neue Datei. -->
|
||||||
|
<script src="assets/js/admin-auth.js?v=20260827-jedesmal"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||||
@@ -3068,13 +3072,14 @@
|
|||||||
document.getElementById("liste-ansicht").style.display = "";
|
document.getElementById("liste-ansicht").style.display = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Nutzer-Wunsch 04.08.2026 (aktualisiert): der Zugangscode soll nur
|
// Nutzer-Wunsch 27.08.2026: der Zugangscode wird wieder bei JEDEM ÖFFNEN
|
||||||
// EINMAL PRO TAG eingegeben werden müssen, nicht bei jedem Besuch (das
|
// der Seite/App verlangt (die Lockerung "nur einmal am Tag" vom 04.08.2026
|
||||||
// war die ursprüngliche, jetzt bewusst gelockerte Regel). admin-auth.js
|
// ist damit aufgehoben). Umgesetzt in admin-auth.js über sessionStorage:
|
||||||
// speichert die Sitzung dafür seit heute in localStorage mit eigenem
|
// gehört zum Tab/App-Fenster, übersteht ein Neuladen (F5, Upload), ist
|
||||||
// 24-Std-Ablauf, der Server erzwingt zusätzlich dasselbe Zeitlimit
|
// beim nächsten Öffnen aber weg. Eine Abmeldung bei Untätigkeit wurde
|
||||||
// (lib/auth.js) — läuft die Sitzung ab, verlangt pruefeSession() ganz
|
// ausdrücklich NICHT gewünscht. Der Server erzwingt zusätzlich sein
|
||||||
// automatisch wieder den Gate-Bildschirm.
|
// eigenes 24-Std-Limit (server-internal/lib/auth.js) — fehlt oder taugt
|
||||||
|
// die Sitzung nicht, zeigt pruefeSession() automatisch wieder das Gate.
|
||||||
pruefeSession();
|
pruefeSession();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user