Eigene Zugangscode-Schranke statt Cloudflare Access — löst VanVans Login-Problem

Ersetzt die (fehlerhaft auf die GANZE Domain wirkende) Cloudflare-Zero-
Trust-Access-Sperre durch eine eigene, schlanke Lösung nach dem bewährten
Vorbild von "Dogfather Universe": eine Pages-Functions-Middleware
(functions/_middleware.js) prüft bei jeder Anfrage ein HMAC-signiertes
Session-Cookie; ohne gültiges Cookie geht's zu public/gate.html (im Van's-
Design), wo ein Zugangscode eingegeben wird. Zwei getrennte Codes für qciga
und VanVan, 90 Tage gültig, danach neu eingeben. /admin/* ist komplett
ausgenommen — läuft weiter über das echte GitHub-OAuth-Login, das für
VanVan schon funktioniert.

Bonus: die drei separaten Vorschau-Seiten (demo-normal/demo-konto/demo-abo)
sind jetzt überflüssig und entfernt. Stattdessen gibt's ein schwebendes
Umschalter-Widget (Layout.astro), das NUR sichtbar ist, wenn das Gate-Cookie
vorhanden ist (also nur für uns beide) — auf jeder beliebigen Seite kann
per Knopfdruck zwischen "Normaler Besucher", "Angemeldete Kundin" und
"Abonnentin" gewechselt werden, ohne die Seite verlassen zu müssen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 18:55:44 +02:00
co-authored by Claude Sonnet 5
parent 6b1920dfb3
commit ad07584906
9 changed files with 502 additions and 161 deletions
+106
View File
@@ -179,10 +179,116 @@ const p = (href: string) => `${localePrefix(lang)}${href}`;
</div>
</footer>
{/* Vorschau-Umschalter: nur sichtbar, wenn im Browser ein gültiges Gate-Zugangscookie liegt
(siehe functions/_middleware.js + public/gate.html) — also nur für uns beide (qciga/vanvan),
niemals für echte Besucher:innen. Ersetzt die früheren separaten demo-Vorschauseiten: EINE
schwebende Box, auf JEDER Seite nutzbar, simuliert per Knopfdruck sofort den gewählten
Konto-Zustand (localStorage) und lädt die aktuelle Seite neu. */}
<div id="vandiy-preview-switcher" style="display:none;">
<button type="button" id="vandiy-preview-toggle" aria-expanded="false" aria-label="Vorschau-Ansicht wählen">🎭</button>
<div id="vandiy-preview-panel">
<p class="small" id="vandiy-preview-current"></p>
<button type="button" data-preview="normal">👤 Normaler Besucher</button>
<button type="button" data-preview="konto">🙋 Angemeldete Kundin</button>
<button type="button" data-preview="abo">🎫 Abonnentin</button>
</div>
</div>
<script>
import { cartCount } from "../scripts/cart";
import { getAccount, logout, anzeigeName } from "../scripts/account";
// Vorschau-Umschalter: Sichtbarkeit hängt allein vom (nicht-httpOnly) Rollen-Cookie ab, das
// die Gate-Middleware nach erfolgreicher Zugangscode-Eingabe setzt.
(function initPreviewSwitcher() {
const match = document.cookie.match(/(?:^|;\s*)vandiy_gate_role=([^;]+)/);
const role = match ? decodeURIComponent(match[1]) : null;
if (!role) return;
const widget = document.getElementById("vandiy-preview-switcher");
const toggle = document.getElementById("vandiy-preview-toggle");
const panel = document.getElementById("vandiy-preview-panel");
const currentLabel = document.getElementById("vandiy-preview-current");
if (!widget || !toggle || !panel) return;
widget.style.display = "block";
const roleLabel = role === "qciga" ? "qciga" : "VanVan";
function beschreibeAktuell() {
try {
const raw = localStorage.getItem("vandiy_account_v1");
const a = raw ? JSON.parse(raw) : null;
if (!a || !a.loggedIn) return "Aktuell: normaler Besucher";
if (a.abo && a.abo.stufe) return "Aktuell: Abonnentin (" + a.abo.stufe + ")";
return "Aktuell: angemeldete Kundin";
} catch {
return "";
}
}
if (currentLabel) currentLabel.textContent = `${beschreibeAktuell()} · als ${roleLabel} angemeldet`;
toggle.addEventListener("click", () => {
const open = panel.classList.toggle("open");
toggle.setAttribute("aria-expanded", open ? "true" : "false");
});
document.addEventListener("click", (e) => {
if (!widget.contains(e.target as Node)) panel.classList.remove("open");
});
const heute = new Date().toISOString().slice(0, 10);
function leeresKonto(zusatz: Record<string, unknown>) {
return {
loggedIn: true,
name: "Vorschau",
username: roleLabel,
email: "[email protected]",
photo: "",
bewerteteBestellungen: [],
treueBestellungen: 0,
treuebonusAktiv: false,
treuebonusArtikelSlug: null,
abo: {
stufe: null, startDatum: null, bezahltBisDatum: null, bezahlteMonate: 0,
gekuendigt: false, gekuendigtAm: null, kleinTeddyFreigeschaltetGesamt: 0,
grossTeddyFreigeschaltetGesamt: 0, kleinTeddyEingeloest: 0, grossTeddyEingeloest: 0,
historie: [],
},
benachrichtigungen: [],
...zusatz,
};
}
const PRESETS: Record<string, () => void> = {
normal: () => localStorage.removeItem("vandiy_account_v1"),
konto: () => localStorage.setItem("vandiy_account_v1", JSON.stringify(leeresKonto({ treueBestellungen: 4 }))),
abo: () => localStorage.setItem("vandiy_account_v1", JSON.stringify(leeresKonto({
treueBestellungen: 6,
abo: {
stufe: "gross", startDatum: "2025-06-03", bezahltBisDatum: "2026-09-03", bezahlteMonate: 14,
gekuendigt: false, gekuendigtAm: null, kleinTeddyFreigeschaltetGesamt: 2,
grossTeddyFreigeschaltetGesamt: 1, kleinTeddyEingeloest: 1, grossTeddyEingeloest: 1,
historie: [],
},
benachrichtigungen: [
{ key: "abo_teddy_klein_frei", datum: heute, gelesen: false },
{ key: "abo_zahlung_erfolgreich", datum: heute, gelesen: true },
{ key: "abo_teddy_gross_eingeloest", datum: heute, gelesen: true },
{ key: "abo_abgeschlossen", datum: "2025-06-03", gelesen: true },
],
}))),
};
panel.querySelectorAll<HTMLButtonElement>("[data-preview]").forEach((btn) => {
btn.addEventListener("click", () => {
const preset = btn.dataset.preview;
if (preset && PRESETS[preset]) {
PRESETS[preset]();
window.location.reload();
}
});
});
})();
function updateCount() {
const el = document.getElementById("cart-count");
if (el) el.textContent = String(cartCount());