/* ===================================================================== functions/_shared/verwaltung-auth.js — ZWEITE, ZUSÄTZLICHE Zugangsschranke NUR für /verwaltung/ + /api/verwaltung/*, unabhängig vom normalen 90-Tage-Seitenzugang (siehe functions/_shared/auth.js). Wer die Bestellübersicht öffnen will, braucht also eine gültige Sitzung auf BEIDEN Ebenen — die Prüfung des eingegebenen Codes selbst läuft dabei nicht hier, sondern in handleVerwaltungAuth() in functions/_middleware.js (dort wird GENAU DERSELBE persönliche Code wie beim normalen Seitenzugang akzeptiert, SITE_ACCESS_CODE_QCIGA/_VANVAN — ursprünglich gab es hier einen dritten, separaten Code, der aber auf ausdrücklichen Wunsch nach echter Verwirrung bei VanVan am 04.08.2026 wieder entfernt wurde). Diese Datei kümmert sich nur noch um die SITZUNG danach: die läuft absichtlich schon nach 24 Stunden ab (statt 90 Tage wie beim normalen Zugang), wodurch derselbe Code hier täglich neu eingegeben werden muss. Bewusst als eigene, kleine Kopie der HMAC-Signaturlogik statt geteiltem Code mit auth.js (gleiches Prinzip wie bei customer-auth.js) — ein Leck bei einer Sitzungsart darf nie die andere gefährden. Eigenes Geheimnis (VERWALTUNG_SESSION_SECRET), eigenes Cookie — auch wenn der geprüfte CODE jetzt derselbe wie beim normalen Zugang ist, bleibt die SITZUNG technisch komplett getrennt (anderes Cookie, andere Gültigkeitsdauer, eigene Signatur). ===================================================================== */ export const VERWALTUNG_COOKIE_NAME = "vandiy_verwaltung_session"; export const VERWALTUNG_SESSION_STUNDEN = 24; function b64urlEncode(bytes) { let bin = ""; bytes.forEach((b) => (bin += String.fromCharCode(b))); return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } function b64urlDecodeToBytes(str) { str = str.replace(/-/g, "+").replace(/_/g, "/"); while (str.length % 4) str += "="; const bin = atob(str); return Uint8Array.from(bin, (c) => c.charCodeAt(0)); } async function hmacKey(secret) { return crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]); } export async function signVerwaltungSession(secret) { const payload = JSON.stringify({ exp: Date.now() + VERWALTUNG_SESSION_STUNDEN * 60 * 60 * 1000 }); const payloadB64 = b64urlEncode(new TextEncoder().encode(payload)); const key = await hmacKey(secret); const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payloadB64)); return `${payloadB64}.${b64urlEncode(new Uint8Array(sig))}`; } export async function verifyVerwaltungSession(token, secret) { if (!token || !token.includes(".")) return null; const [payloadB64, sigB64] = token.split("."); try { const key = await hmacKey(secret); const valid = await crypto.subtle.verify("HMAC", key, b64urlDecodeToBytes(sigB64), new TextEncoder().encode(payloadB64)); if (!valid) return null; const payload = JSON.parse(new TextDecoder().decode(b64urlDecodeToBytes(payloadB64))); if (!payload.exp || payload.exp < Date.now()) return null; return payload; } catch { return null; } } export function getCookie(request, name) { const header = request.headers.get("Cookie") || ""; const match = header.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`)); return match ? decodeURIComponent(match[1]) : null; } /** true, sofern eine gültige, noch nicht abgelaufene Tages-Sitzung vorliegt. Für API-Routen * gedacht, die unabhängig vom Seiten-Routing (Middleware) selbst nochmal prüfen wollen — siehe * functions/api/verwaltung/*, genau wie getGateRole() in auth.js für den normalen Zugang. */ export async function hatGueltigeVerwaltungSitzung(request, env) { if (!env.VERWALTUNG_SESSION_SECRET) return false; const token = getCookie(request, VERWALTUNG_COOKIE_NAME); const payload = token ? await verifyVerwaltungSession(token, env.VERWALTUNG_SESSION_SECRET) : null; return !!payload; }