diff --git a/server/pruef-glocke.mjs b/server/pruef-glocke.mjs index 4faafb49..f9fbedbf 100644 --- a/server/pruef-glocke.mjs +++ b/server/pruef-glocke.mjs @@ -44,9 +44,17 @@ GEGENPROBEN durchgehend. */ -import { mkdtempSync, rmSync, appendFileSync } from "node:fs"; +import { mkdtempSync, rmSync, appendFileSync, readdirSync, readFileSync } + from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +/* WO DIE SEITEN LIEGEN. Abgeleitet vom eigenen Ort, nicht als + fester Pfad -- damit die Pruefung auch laeuft, wenn jemand das + Verzeichnis woandershin legt. */ +const SEITEN_ORDNER = join(dirname(fileURLToPath(import.meta.url)), + "..", "workspace"); const ordner = mkdtempSync(join(tmpdir(), "ws-glocke-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); @@ -84,7 +92,7 @@ const hinweis = (t) => melde(" -- " + t); /* ---- Bestand ------------------------------------------------------------ */ const { DatabaseSync } = await import("node:sqlite"); -const { scryptSync, randomBytes } = await import("node:crypto"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); await fetch(BASIS + "/workspace/api/anmelden", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {}); @@ -96,17 +104,38 @@ for (let i = 0; i < 60; i++) { } const d = new DatabaseSync(process.env.WORKSPACE_DB); const jetzt = new Date().toISOString(); +/* DER SUCHSCHLUESSEL FUER DEN STILLEN ZUGANG (24.09.2026). + + Rechte Hand, linke Hand und Modi haben keine eigene Kachel; der + Server findet sie ueber `code_kennung`, einen HMAC des Codes. + Fehlt diese Spalte, kommen sie gar nicht herein -- und das sah in + dieser Pruefung aus wie ein Fehler an der Glocke, war aber einer + am Aufbau der Testdaten. */ +const kennSchluessel = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", kennSchluessel, jetzt); function anlegen(name, rolle, code) { const salt = randomBytes(16).toString("hex"); const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); - d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)") - .run(name, rolle, hash, salt, 32768, jetzt); + d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n," + + " code_kennung, aktiv, erstellt) VALUES (?,?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, + createHmac("sha256", kennSchluessel).update(code).digest("hex"), jetzt); return d.prepare("SELECT last_insert_rowid() AS id").get().id; } const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001"); anlegen("Cigdem", "manager", "CODE-MANA-0001"); const idScout = anlegen("Patrick", "scout", "CODE-SCOU-0001"); const idLuna = anlegen("Luna", "creator", "CODE-CREA-0001"); +/* DIE UEBRIGEN VIER ROLLEN (24.09.2026). + Sie fehlten, und deshalb fehlten sie auch im Abschnitt "Jede Rolle + bekommt ihn" weiter unten -- der pruefte vier von acht und hiess + trotzdem "jede". */ +anlegen("VanVan", "hand", "CODE-HAND-0001"); +anlegen("Funny", "linke", "CODE-LINK-0001"); +anlegen("Miss", "modi", "CODE-MODI-0001"); +anlegen("Spicy Media", "spicy", "CODE-SPIC-0001"); d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idLuna, idScout, jetzt); d.close(); @@ -179,7 +208,22 @@ function berechtigung(stand) { async function anmelden(ctx, rolle, code) { const seite = await ctx.newPage(); await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" }); - await seite.click(`.rolle[data-rolle="${rolle}"]`); + /* DER STILLE ZUGANG (09.09.2026, hier nachgezogen am 24.09.2026). + + Rechte Hand, linke Hand und Modi haben ABSICHTLICH keine eigene + Kachel auf der Anmeldeseite -- Filipes Wunsch: "damit die von der + workspace auch nicht mal sehen dass die modis von mir einen + eigenen zugang haben." Sie tippen auf irgendeine vorhandene + Kachel, und der Code allein entscheidet. + + Solange diese Pruefung nur vier Rollen ansah, fiel es nicht auf. + Seit sie alle acht ansieht, waere `click` auf eine Kachel, die + es nicht gibt, ein Abbruch -- und der saehe aus wie ein Fehler + an der Glocke, obwohl er einer an der Pruefung waere. */ + const kachel = await seite.$(`.rolle[data-rolle="${rolle}"]`) + || await seite.$(".rolle"); + if (!kachel) throw new Error("keine einzige Rollenkachel auf der Anmeldeseite"); + await kachel.click(); await seite.fill("#code", code); await seite.click("button[type=submit]"); await seite.waitForURL("**/start.html", { timeout: 25000 }); @@ -236,8 +280,39 @@ melde("\n=== Der Knopf ist überall ==="); await ctx.addInitScript(berechtigung("default")); const seite = await anmelden(ctx, "admin", "CODE-DOGI-0001"); - const seitenNamen = ["start", "aufgaben", "kalender", "bereich", "content", - "dateien", "wissen", "report"]; + /* DIE LISTE WIRD ABGELEITET, NICHT ABGESCHRIEBEN (24.09.2026). + + Hier standen acht Namen -- und der Abschnitt darueber heisst + "Der Knopf ist ueberall". Er war gruen, weil alle acht ihn + hatten. Gemessen am selben Tag: VIERZEHN WEITERE Seiten haben + eine Kopfleiste und hatten keinen Knopf, darunter befinden.html, + werdegang.html und teilen.html -- genau die Seiten, auf denen + eine Creator ihre Zeit verbringt, und talente.html und + bewerbungen.html, wo ein Scout sitzt. Gemeldet hat es Filipe + ("dieser button soll bei jeder rolle perfekt funktionieren"), + nicht diese Pruefung. + + DIE LISTE KOMMT JETZT AUS DEM VERZEICHNIS: Jede Seite, die eine + Kopfleiste hat, muss den Knopf haben. Eine Seite, die morgen + dazukommt, ist damit von selbst dabei -- eine Liste, die niemand + pflegt, kann nicht veralten. */ + const alleSeiten = readdirSync(SEITEN_ORDNER) + .filter((f) => f.endsWith(".html")) + .filter((f) => readFileSync(join(SEITEN_ORDNER, f), "utf8") + .includes("kopfleiste__rechts")) + .map((f) => f.replace(/\.html$/, "")); + /* EINE BEGRUENDETE AUSNAHME, und nur eine: anruf-probe.html ist + eine eigenstaendige Diagnoseseite ohne kopf.js, und + anruf-probe.js verweist ausdruecklich auf "im Chat oben auf die + Glocke tippen". Sie steht hier NAMENTLICH -- eine stille + Ausnahme waere ein Loch mit Zustimmung. */ + const AUSNAHMEN = new Set(["anruf-probe"]); + const seitenNamen = alleSeiten.filter((x) => !AUSNAHMEN.has(x)); + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG: Faende das Verzeichnis morgen + keine Seite mehr, waere "auf allen 0 geprueften" sonst gruen. */ + ok(seitenNamen.length >= 20, + `${seitenNamen.length} Seiten mit Kopfleiste gefunden ` + + `(ausgenommen: ${[...AUSNAHMEN].join(", ")})`); let gefunden = 0; const ohne = []; for (const n of seitenNamen) { @@ -245,9 +320,9 @@ melde("\n=== Der Knopf ist überall ==="); await seite.waitForTimeout(250); if (await seite.$("#glocke")) gefunden++; else ohne.push(n); } - ok(gefunden === seitenNamen.length, + ok(gefunden === seitenNamen.length && gefunden > 0, ohne.length ? `fehlt auf: ${ohne.join(", ")}` - : `auf allen ${gefunden} geprüften Seiten`); + : `auf allen ${gefunden} Seiten mit Kopfleiste`); /* Und NICHT auf der Anmeldeseite -- dort ist niemand angemeldet, den man benachrichtigen könnte. */ @@ -262,8 +337,14 @@ melde("\n=== Jede Rolle bekommt ihn ==="); { const browser = await pw.chromium.launch(); for (const [name, rolle, code] of [ + /* ALLE ACHT (24.09.2026). Hier standen vier -- unter der + Ueberschrift "Jede Rolle bekommt ihn". Die rechte Hand, die + linke Hand, die Modis und Spicy Media fehlten; ausgerechnet + die Modis sind die groesste Gruppe im Haus. */ ["DogFather", "admin", "CODE-DOGI-0001"], ["Manager", "manager", "CODE-MANA-0001"], ["Scout", "scout", "CODE-SCOU-0001"], ["Creator", "creator", "CODE-CREA-0001"], + ["Rechte Hand", "hand", "CODE-HAND-0001"], ["Linke Hand", "linke", "CODE-LINK-0001"], + ["Modi", "modi", "CODE-MODI-0001"], ["Spicy Media", "spicy", "CODE-SPIC-0001"], ]) { const ctx = await browser.newContext(); await ctx.addInitScript(berechtigung("default")); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index d130166c..bae2b423 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +