Zweite, tägliche Zugangsschranke nur für die Bestellverwaltung
Auf ausdrücklichen Wunsch: /verwaltung/ + /api/verwaltung/* verlangen jetzt ZUSÄTZLICH zum normalen 90-Tage-Seitenzugang einen zweiten, eigenen Code, den nur qciga und VanVan kennen — und der nach 24 Stunden automatisch abläuft (muss also einmal täglich neu eingegeben werden). Der Rest der Seite ist davon unberührt, nur die Bestellübersicht ist betroffen. Bewusst als eigenständiges System (eigenes Cookie vandiy_verwaltung_session, eigenes Geheimnis VERWALTUNG_SESSION_SECRET, eigener HMAC-Signaturcode statt geteiltem mit dem normalen Seitenzugang) — gleiches Trennungsprinzip wie bei den Kundenkonten: ein Leck bei einer Sitzungsart darf nie die andere gefährden. Neue Dateien: functions/_shared/verwaltung-auth.js (Session-Logik), public/verwaltung-code.html (Eingabemaske im selben Stil wie die Haupt-Zugangsseite). functions/_middleware.js prüft nach erfolgreicher Haupt-Sitzung zusätzlich diese zweite Sitzung für /verwaltung*-Pfade. Beide API-Routen (orders.js, orders/[id].js) prüfen die zweite Schranke zusätzlich eigenständig, nach demselben Verteidigungs-in-der-Tiefe-Prinzip wie die bestehende Haupt-Prüfung. Lokal vollständig durchgetestet (wrangler pages dev): kompletter Ablauf ohne jeden Zugang -> Haupt-Gate -> Redirect zum Tages-Code -> falscher Code abgelehnt -> richtiger Code setzt 24-Stunden-Cookie -> Seite und API danach erreichbar. Bestätigt: der Rest der Seite bleibt unberührt (kein Tages-Code nötig), UND dass VanVan mit nur dem Haupt-Zugang (ohne Tages-Code) ebenfalls noch ausgesperrt bleibt, wie gewünscht. Zwei neue Cloudflare-Secrets in Produktion gesetzt: VERWALTUNG_SESSION_SECRET (zufällig erzeugt) und VERWALTUNG_ACCESS_CODE (der eigentliche Tages-Code, an qciga und VanVan zu verteilen). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7e02879f66
commit
aff961816b
@@ -0,0 +1,70 @@
|
||||
/* =====================================================================
|
||||
functions/_shared/verwaltung-auth.js — ZWEITE, ZUSÄTZLICHE Zugangsschranke NUR für
|
||||
/verwaltung/ + /api/verwaltung/* — auf ausdrücklichen Wunsch: ein einziger, gemeinsamer
|
||||
Tages-Code, den nur qciga und VanVan kennen, unabhängig vom normalen 90-Tage-Seitenzugang
|
||||
(siehe functions/_shared/auth.js). Wer die Bestellübersicht öffnen will, braucht also BEIDE
|
||||
Codes — den normalen Seitenzugang UND diesen hier, und dieser hier läuft nach 24 Stunden ab.
|
||||
|
||||
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, eigener
|
||||
einzelner Code (VERWALTUNG_ACCESS_CODE) statt getrennter Codes pro Person — hier reicht ein
|
||||
gemeinsamer Code, weil es nur um diese eine zusätzliche Hürde geht, nicht um die Anzeige
|
||||
einer rollenabhängigen Vorschau wie beim normalen Seitenzugang. ===================================================================== */
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user