From 63a3fb4af8d9f71ea876f25815b724a2685b7bf5 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Fri, 25 Sep 2026 02:47:07 +0200 Subject: [PATCH] Die rechte Hand sieht die Personenseite wirklich -- Liste, Rollenkarten und das Protokoll Filipe, zum wiederholten Mal und mit einem Bildschirmfoto genau dieser Seite: "zum hunderstenmal, also bitte mach dass es jetzt endlich klappt, die rechte hand sieht das immer noch nicht obwohl ich will dass die rechte hand das auch sieht." ZUERST NACHGEMESSEN, NICHT GERATEN. Am 24.09. habe ich auf ein Bildschirmfoto hin an der falschen Seite gebaut und es im Commit selbst notiert. Diesmal zuerst mess-hand-personen.mjs: dieselbe Seite, zwei Anmeldungen, und der Unterschied wird aufgezaehlt. Ergebnis in einer Zeile -- sie bekam vom Server alle acht Personen (HTTP 200) und sah auf dem Bildschirm NICHTS davon. Nur das Anlege-Formular, darueber der Satz "Codes, Sperren und das Protokoll bleiben bei DogFather". ZWEI URSACHEN, UND NUR EINE WAR EINE SCHRANKE: 1. Die OBERFLAECHE hat die Liste versteckt, die sie laengst geladen hatte. `personen.js` entschied die Ausbaustufe mit `ich.rolle !== 'admin'`, setzte damit `data-nur-anlegen`, und `personen.css` blendet darauf hin die Liste, das Protokoll und "Alle aufklappen" aus. Diese CSS-Regel stammt vom 07.09. und war fuer Manager und Spicy Media gedacht; die rechte Hand ist erst danach dazugekommen und fiel stillschweigend mit hinein. Das ist in dieser einen Datei die DRITTE Stelle, an der ein Rollenvergleich im Browser veraltet ist -- nach dem 22.09. ("keine Knoepfe") und dem 24.09. ("keine Rollenwahl"). Jedes Mal hatte sie das Recht und sah es nicht. 2. Das Protokoll war am Server zu (HTTP 404). Damit ist der Satz von oben ueberholt: Filipes Ansage vom 24.09. -- "die selben rechte da haben wie dogfather, das einzige was sie nicht kann ist die dogfather rolle oder leute anfassen" -- laesst dafuer keinen Rest. EINE AUSKUNFT FUER DREI STELLEN. `fuehrtDieZugaenge(person)` steht jetzt in workspace.js und beantwortet dieselbe Frage fuer die Tuer am Server, fuer `/api/ich` (`darf_zugaenge_fuehren`) und fuer die Ausbaustufe der Seite. Drei Abschriften waeren drei Gelegenheiten, dass die naechste Aenderung nur zwei davon trifft -- genau so ist dieser Fehler entstanden. `istHand` WAERE FALSCH GEWESEN. Es fasst beide Haende zusammen, und fuer die linke gilt ausdruecklich das Gegenteil ("sieht weder Bewerbungen noch den vertraulichen Meldeweg"). Wer hier den Sammelbegriff nimmt, dreht eine ausgesprochene Entscheidung stillschweigend um. Die Prueflung fragt sie deshalb einzeln. DIE PRUEFUNG ZIEHT NACH (40 -> 49). Abschnitt 6 prueft beides: dass die rechte Hand dasselbe Protokoll bekommt wie DogFather, und dass die Auskunft, aus der die Oberflaeche ihre Ausbaustufe baut, mit der Tuer am Server uebereinstimmt. Genau dieser Abgleich hat gefehlt: Eine Rechtepruefung, die nur Serverantworten ansieht, hat den Fehler zwei Tage lang nicht bemerkt. Dazu drei Gegenproben (linke Hand 404, Modi 404, linke Hand `darf_zugaenge_fuehren === false`). Beim ersten Lauf waren diese Gegenproben rot -- mit 401 statt 404. Die Abschnitte davor sperren und loeschen absichtlich Leute, und eine tote Sitzung antwortet mit 401: Das sieht aus wie "darf nicht" und heisst "gibt es nicht mehr". Ein 401 als Gegenprobe fuer ein 404 ist ein Haken ohne Gegenstand. Abschnitt 6 legt sich deshalb frische Zugaenge an. Geprueft: pruef-hand-personen (49, 0 Fehler), pruef-personen-liste, pruef-personen-kachel (45), pruef-personen-loeschen, pruef-modi-verborgen (85). Unveraendert rot und an HEAD nachgemessen, also nicht von diesem Umbau: pruef-personen-formular (2), pruef-community-sicht (1), pruef-spicy (3). Co-Authored-By: Claude Opus 5 --- server/mess-hand-personen.mjs | 242 ++++++++++++++++++++++++++++++++ server/pruef-hand-personen.mjs | 120 ++++++++++++++++ server/workspace-personen.js | 30 +++- server/workspace.js | 42 ++++++ workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/js/personen.js | 28 +++- workspace/aufgaben.html | 38 ++--- workspace/automation.html | 32 ++--- workspace/befinden.html | 34 ++--- workspace/bereich.html | 44 +++--- workspace/bewerben.html | 38 ++--- workspace/bewerbungen.html | 36 ++--- workspace/calls.html | 34 ++--- workspace/chat.html | 38 ++--- workspace/content.html | 34 ++--- workspace/crew-index.html | 12 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 ++--- workspace/entwicklung.html | 36 ++--- workspace/hilfe.html | 30 ++-- workspace/index.html | 10 +- workspace/kalender.html | 34 ++--- workspace/leistung.html | 32 ++--- workspace/material.html | 32 ++--- workspace/personen.html | 32 ++--- workspace/profil.html | 36 ++--- workspace/rechte.html | 34 ++--- workspace/report.html | 36 ++--- workspace/scouting.html | 32 ++--- workspace/start.html | 32 ++--- workspace/startcheck.html | 34 ++--- workspace/steckbrief.html | 36 ++--- workspace/support.html | 28 ++-- workspace/talente.html | 36 ++--- workspace/team.html | 32 ++--- workspace/teamlage.html | 34 ++--- workspace/teilen.html | 30 ++-- workspace/treff-moderation.html | 32 ++--- workspace/treff-regeln.html | 32 ++--- workspace/uebersicht.html | 32 ++--- workspace/unsere-seiten.html | 30 ++-- workspace/unterstuetzen.html | 32 ++--- workspace/werdegang.html | 32 ++--- workspace/willkommen.html | 30 ++-- workspace/wissen.html | 34 ++--- 46 files changed, 1087 insertions(+), 635 deletions(-) create mode 100644 server/mess-hand-personen.mjs diff --git a/server/mess-hand-personen.mjs b/server/mess-hand-personen.mjs new file mode 100644 index 00000000..60338070 --- /dev/null +++ b/server/mess-hand-personen.mjs @@ -0,0 +1,242 @@ +/* ===================================================================== + WAS SIEHT DIE RECHTE HAND AUF DER PERSONENSEITE? (25.09.2026) + + --------------------------------------------------------------------- + WARUM ES DIESE DATEI GIBT + + Filipe, zum wiederholten Mal: „die rechte hand sieht das immer noch + nicht obwohl ich will dass die rechte hand das auch sieht." Dazu ein + Bildschirmfoto der Personenseite -- Rollenkarten oben, „Letzte + Ereignisse" unten. + + ICH KOENNTE RATEN, welches „das" gemeint ist. Am 24.09.2026 habe ich + genau das getan, auf ein Bildschirmfoto hin an der falschen Seite + gebaut und es im Commit selbst notiert („ZUERST EIN IRRTUM VON MIR"). + Diesmal wird nachgesehen, bevor gebaut wird: Zwei Anmeldungen, + dieselbe Seite, und die Unterschiede werden AUFGEZAEHLT. + + SIE PRUEFT NICHTS und meldet keinen Fehlschlag. Sie macht eine Liste + und zwei Bilder, auf denen man nachsehen kann. Deshalb `mess-` und + nicht `pruef-`: Die Portvergabe leitet sich aus den `pruef-`-Namen + ab, und eine Messdatei darf die Nummern der Pruefungen nicht + verschieben. + + EIGENE WEGWERF-DATENBANK, NIE DIE ECHTE. Es entstehen echte Personen + und echte Protokollzeilen; in einem laufenden System waeren das + Testdaten, und die sind verboten. + + Aufrufen mit: node server/mess-hand-personen.mjs + ===================================================================== */ +import { mkdtempSync, rmSync, readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { execFileSync } from "node:child_process"; +import { request as httpAnfrage } from "node:http"; +import { createServer as httpsServer } from "node:https"; + +const PORT = 5405, HP = 5406; +const CREW = "crew.dogfather-universe.com"; +const ordner = mkdtempSync(join(tmpdir(), "ws-hand-pers-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(PORT); +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { + return ec.call(this, n, w, { ...(o || {}), secure: false }); +}; +import { notbremse } from "./helfer-notbremse.mjs"; +await import("./index.js"); +notbremse(240_000, "mess-hand-personen"); +await new Promise((r) => setTimeout(r, 900)); + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); +process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); }); + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const jetzt = new Date().toISOString(); +const sch = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", sch, jetzt); + +const anlegen = (name, rolle, code) => { + const s = randomBytes(16).toString("hex"); + const h = scryptSync(code, s, 64, + { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + return d.prepare("INSERT INTO personen" + + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,?,1,?) RETURNING id") + .get(name, rolle, h, s, 32768, + createHmac("sha256", sch).update(code).digest("hex"), jetzt).id; +}; + +/* Dieselbe Besetzung wie auf Filipes Bildschirmfoto: beide Haende, + mehrere Modis, mehrere aus der Community. */ +anlegen("Filipe", "admin", "CODE-DOGI-0001"); +anlegen("VanVan", "hand", "CODE-HAND-0001"); +anlegen("Kessi", "linke", "CODE-LINKE-0001"); +anlegen("Diene", "modi", "CODE-MODI-0001"); +anlegen("Marina", "modi", "CODE-MODI-0002"); +anlegen("Ghost", "modi", "CODE-MODI-0003"); +anlegen("Anni", "gast", "CODE-GAST-0001"); +anlegen("Cleo", "gast", "CODE-GAST-0002"); +/* Ein paar Protokollzeilen, damit „Letzte Ereignisse" nicht leer ist -- + sonst sieht „sieht nichts" bei beiden Rollen gleich aus. */ +for (let i = 0; i < 6; i++) { + d.prepare("INSERT INTO protokoll (zeitpunkt, person_id, rolle, aktion, detail, ip)" + + " VALUES (?,1,'admin','einstellung_geaendert',?,'127.0.0.1')") + .run(new Date(Date.now() - i * 60000).toISOString(), `probe_${i}`); +} +d.close(); + +const anfrage = (pfad, art, koerper, keks) => new Promise((fertig, schief) => { + const a = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: pfad, method: art, + headers: { + Host: CREW, "Content-Type": "application/json", + ...(keks ? { Cookie: keks } : {}), + }, + }, (r) => { + let t = ""; r.on("data", (x) => { t += x; }); + r.on("end", () => fertig({ code: r.statusCode, kopf: r.headers, text: t })); + }); + a.on("error", schief); + if (koerper) a.write(JSON.stringify(koerper)); + a.end(); +}); + +const anmelden = async (rolle, code) => { + const a = await anfrage("/workspace/api/anmelden", "POST", { rolle, code }); + if (a.code !== 200) { + console.log(`ABBRUCH: ${rolle} ließ sich nicht anmelden (HTTP ${a.code}) ${a.text.slice(0, 120)}`); + process.exit(2); + } + return (a.kopf["set-cookie"] || []).map((x) => x.split(";")[0]).join("; "); +}; + +const kekse = { + DogFather: await anmelden("admin", "CODE-DOGI-0001"), + "rechte Hand": await anmelden("hand", "CODE-HAND-0001"), +}; + +/* ---- Erst die ANTWORTEN vergleichen, dann den Bildschirm ------------ + Wenn der Server schon verschieden antwortet, muss man im Browser gar + nicht mehr suchen. */ +console.log("\n=== Was der Server den beiden gibt ==="); +for (const [wer, keks] of Object.entries(kekse)) { + const p = await anfrage("/workspace/api/verwaltung/personen", "GET", null, keks); + const prot = await anfrage("/workspace/api/verwaltung/protokoll?anzahl=25", "GET", null, keks); + let leute = 0, rollen = ""; + try { + const j = JSON.parse(p.text); + leute = (j.personen || []).length; + rollen = [...new Set((j.personen || []).map((x) => x.rolle))].join(", "); + } catch { /* zeigt sich am Code */ } + let zeilen = -1; + try { zeilen = (JSON.parse(prot.text).eintraege || []).length; } catch { /* egal */ } + console.log(` ${wer.padEnd(12)} Personen HTTP ${p.code} → ${leute} Leute (${rollen})`); + console.log(` ${"".padEnd(12)} Protokoll HTTP ${prot.code} → ${zeilen < 0 ? prot.text.slice(0, 60) : zeilen + " Zeilen"}`); +} + +const schl = join(ordner, "b.key"), zert = join(ordner, "b.crt"); +execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-keyout", schl, + "-out", zert, "-days", "2", "-subj", `/CN=${CREW}`, "-addext", `subjectAltName=DNS:${CREW}`], +{ stdio: "ignore" }); +const vorbau = httpsServer({ key: readFileSync(schl), cert: readFileSync(zert) }, + (von, nach) => { + const w = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: von.url, method: von.method, + headers: { ...von.headers, host: `${CREW}:${HP}` }, + }, (a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); }); + von.pipe(w); + }); +await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r)); + +const pw = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await pw.chromium.launch({ + args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"], +}); +const MARKE = process.env.MARKE || "hand"; +const befund = {}; +try { + for (const [wer, keks] of Object.entries(kekse)) { + const kontext = await browser.newContext({ viewport: { width: 1440, height: 1500 } }); + await kontext.addCookies(keks.split("; ").map((k) => { + const [n, ...r] = k.split("="); + return { name: n, value: r.join("="), domain: CREW, path: "/" }; + })); + const seite = await kontext.newPage(); + const konsole = []; + seite.on("console", (e) => { + if (e.type() === "error" && !/favicon|ERR_CERT|net::ERR/i.test(e.text())) { + konsole.push(e.text().slice(0, 140)); + } + }); + await seite.goto(`https://${CREW}:${HP}/workspace/personen.html`, + { waitUntil: "networkidle" }); + await seite.waitForTimeout(1800); + + const lage = await seite.evaluate(() => { + const sicht = (e) => !!(e && e.offsetParent !== null); + const text = (s) => (document.querySelector(s)?.textContent || "").trim(); + return { + wo: location.pathname, + gruppen: [...document.querySelectorAll(".gruppe__titel, .rollen-block__titel, h2")] + .map((e) => e.textContent.trim()).filter(Boolean).slice(0, 12), + karten: [...document.querySelectorAll("[data-rolle]")] + .map((e) => e.dataset.rolle).filter((x, i, a) => x && a.indexOf(x) === i), + knoepfe: [...document.querySelectorAll("button")] + .filter(sicht).map((e) => e.textContent.trim()) + .filter(Boolean).filter((x, i, a) => a.indexOf(x) === i).slice(0, 25), + protokollBlock: !!document.querySelector(".protokoll-block"), + protokollSichtbar: sicht(document.querySelector(".protokoll-block")), + protokollZeilen: document.querySelectorAll(".protokoll-zeile").length, + protokollText: text("#protokoll").slice(0, 60), + schalter: sicht(document.getElementById("protokoll-schalter")), + }; + }); + befund[wer] = lage; + await seite.screenshot({ + path: `server/mess-${MARKE}-personen-${wer === "DogFather" ? "dogi" : "hand"}.png`, + fullPage: true, + }); + if (konsole.length) console.log(` [${wer}] Browser meldet: ${konsole[0]}`); + await kontext.close(); + } +} finally { + await browser.close(); + await new Promise((r) => vorbau.close(r)); +} + +console.log("\n=== Was auf dem Bildschirm steht ==="); +for (const [wer, l] of Object.entries(befund)) { + console.log(`\n --- ${wer} (${l.wo}) ---`); + console.log(` Rollen-Karten: ${l.karten.join(", ") || "(keine)"}`); + console.log(` Protokoll-Block: ${l.protokollBlock ? "im Dokument" : "FEHLT"}` + + ` / sichtbar: ${l.protokollSichtbar}` + + ` / Zeilen: ${l.protokollZeilen}` + + ` / Schalter: ${l.schalter}`); + console.log(` Protokoll-Text: ${JSON.stringify(l.protokollText)}`); + console.log(` Knoepfe: ${l.knoepfe.join(" · ")}`); +} + +/* DER UNTERSCHIED IST DIE ANTWORT -- alles andere ist Beiwerk. */ +const a = befund.DogFather, b = befund["rechte Hand"]; +if (a && b) { + console.log("\n=== Der Unterschied ==="); + const fehltKarte = a.karten.filter((x) => !b.karten.includes(x)); + const fehltKnopf = a.knoepfe.filter((x) => !b.knoepfe.includes(x)); + console.log(` Karten, die ihr fehlen: ${fehltKarte.join(", ") || "(keine)"}`); + console.log(` Knoepfe, die ihr fehlen: ${fehltKnopf.join(" · ") || "(keine)"}`); + console.log(` Protokollzeilen: DogFather ${a.protokollZeilen}, rechte Hand ${b.protokollZeilen}`); +} +console.log(`\n Bilder: server/mess-${MARKE}-personen-{dogi,hand}.png\n`); + +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } +process.exit(0); diff --git a/server/pruef-hand-personen.mjs b/server/pruef-hand-personen.mjs index 2801ae39..6877abaa 100644 --- a/server/pruef-hand-personen.mjs +++ b/server/pruef-hand-personen.mjs @@ -325,6 +325,126 @@ melde("\n=== 5. Gegenproben (jede MUSS anschlagen) ==="); + `${[...new Set(namen)].join(", ")})`); } +/* ===================================================================== + 6. DAS PROTOKOLL UND DIE AUSBAUSTUFE DER SEITE (25.09.2026) + ===================================================================== + + Filipe, zum wiederholten Mal und mit einem Bildschirmfoto der + Personenseite: „die rechte hand sieht das immer noch nicht obwohl + ich will dass die rechte hand das auch sieht." + + ZWEI DINGE FEHLTEN, und nur eines davon war eine Schranke: + + 1. Der Server antwortete ihr auf das Protokoll mit 404. + 2. Die OBERFLAECHE hat ihr die Liste versteckt, die sie laengst + geladen hatte -- `personen.js` entschied die Ausbaustufe mit + `ich.rolle !== "admin"`, und `personen.css` blendet bei + `data-nur-anlegen` die Liste aus. Gemessen mit + mess-hand-personen.mjs: acht Personen in der Antwort, null auf + dem Bildschirm. + + BEIDES WIRD HIER GEPRUEFT, und das zweite ist das wichtigere: Eine + Rechtepruefung, die nur Serverantworten ansieht, haette diesen + Fehler zwei Tage lang nicht bemerkt -- genau so ist es gewesen. + Deshalb steht die Auskunft, aus der die Oberflaeche ihre + Ausbaustufe baut (`darf_zugaenge_fuehren`), hier ausdruecklich mit + drin. + + MIT GEGENPROBEN: Was die rechte Hand darf, darf die LINKE nicht -- + fuer sie gilt ausdruecklich „sieht weder Bewerbungen noch den + vertraulichen Meldeweg", und ein Sammelbegriff (`istHand`) haette + diese Entscheidung stillschweigend umgedreht. + ===================================================================== */ +melde("\n=== 6. Das Protokoll und die Ausbaustufe ==="); +{ + const dogiProt = json(await roh("/workspace/api/verwaltung/protokoll?anzahl=25", kDogi)); + const handProt = await roh("/workspace/api/verwaltung/protokoll?anzahl=25", kHand); + const handJson = json(handProt); + /* DIE ANZAHL STEHT IN DER BEDINGUNG. Waere das Protokoll leer, + waeren „0 Zeilen fuer beide" gruen, ohne dass irgendjemand + irgendetwas sieht. Es ist nie leer -- die Anmeldungen dieser + Pruefung stehen selbst darin. */ + const dogiZeilen = (dogiProt.eintraege || []).length; + const handZeilen = (handJson.eintraege || []).length; + ok(dogiZeilen > 0, `DogFather sieht das Protokoll (${dogiZeilen} Zeilen)`); + ok(handProt.code === 200 && handZeilen === dogiZeilen, + `die rechte Hand sieht dasselbe Protokoll (HTTP ${handProt.code}, ` + + `${handZeilen} Zeilen, DogFather ${dogiZeilen})`); + + /* ---- FRISCHE ZUGAENGE FUER DIE GEGENPROBEN --------------------- + + Die aus Abschnitt 1 sind hier nicht mehr zu gebrauchen: Die + Abschnitte davor SPERREN und LOESCHEN absichtlich Leute, und eine + tote Sitzung antwortet mit 401. Das sieht aus wie „darf nicht" + und heisst „gibt es nicht mehr" -- ein 401 als Gegenprobe fuer + ein 404 waere ein Haken ohne Gegenstand. Beim ersten Lauf ist + genau das passiert, viermal. + + Angelegt wird direkt in der Datenbank, wie oben: ueber die Route + ginge es auch, aber dann haengt die Gegenprobe an einem Recht, + das sie gar nicht pruefen will. */ + const d3 = new DatabaseSync(process.env.WORKSPACE_DB); + const frisch = (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"); + d3.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", ks).update(code).digest("hex"), + new Date().toISOString()); + }; + frisch("Kessi2", "linke", "CODE-LINK-0002"); + frisch("Miss2", "modi", "CODE-MODI-0009"); + d3.close(); + const kLinke2 = await anmelden("linke", "CODE-LINK-0002"); + const kModi2 = await anmelden("modi", "CODE-MODI-0009"); + + /* GEGENPROBE 1: die linke Hand. Sie steht in derselben Menge wie die + rechte (`WIE_RECHTE_HAND`), und genau deshalb wird sie hier + einzeln gefragt: Haette jemand `istHand` statt der Rolle benutzt, + bekaeme sie das Protokoll mit -- unbemerkt. */ + const linkeProt = await roh("/workspace/api/verwaltung/protokoll?anzahl=25", kLinke2); + ok(linkeProt.code === 404, + `Gegenprobe: die linke Hand sieht es nicht (${linkeProt.code}) — ` + + `„keine Bewerbungen, kein vertraulicher Meldeweg" gilt unveraendert`); + + /* GEGENPROBE 2: ein Modi. Wer hier durchkaeme, saehe jede Anmeldung + mit IP-Adresse. */ + const modiProt = await roh("/workspace/api/verwaltung/protokoll?anzahl=25", kModi2); + ok(modiProt.code === 404, `Gegenprobe: ein Modi sieht es nicht (${modiProt.code})`); + /* DASS DIE FRISCHEN ZUGAENGE WIRKLICH LEBEN, steht in der Bedingung + der naechsten Zeilen: `darf_zugaenge_fuehren === false` bekommt + man nur aus einer echten Antwort. Bei einer toten Sitzung stuende + dort `undefined`, und die Zeile waere rot -- so wie beim ersten + Lauf. */ + + /* ---- UND DIE AUSKUNFT, AUS DER DIE SEITE IHRE STUFE BAUT ------- + Sie ist der eigentliche Fund vom 25.09.2026: Der Server war + grosszuegiger als die Oberflaeche, und niemand hat es gemerkt, + weil keine Pruefung die Oberflaeche gefragt hat. */ + const ichDogi = json(await roh("/workspace/api/ich", kDogi)); + const ichHand = json(await roh("/workspace/api/ich", kHand)); + const ichLinke = json(await roh("/workspace/api/ich", kLinke2)); + ok(ichDogi.darf_zugaenge_fuehren === true, + `DogFather bekommt die volle Seite gesagt (${ichDogi.darf_zugaenge_fuehren})`); + ok(ichHand.darf_zugaenge_fuehren === true, + `und die rechte Hand genauso (${ichHand.darf_zugaenge_fuehren}) — ` + + `daran haengen Liste, Rollenkarten und Protokoll`); + ok(ichLinke.darf_zugaenge_fuehren === false, + `Gegenprobe: die linke Hand nicht (${ichLinke.darf_zugaenge_fuehren})`); + + /* DIE AUSKUNFT MUSS ZUR TUER PASSEN. Zwei Werte, die dasselbe sagen + sollen, laufen sonst auseinander -- und dann zeigt die Seite einen + Kasten, den der Server mit 404 beantwortet. Genau diese Sorte + Widerspruch hat in dieser Datei am 22. und 24.09. schon zweimal + Rechte verschluckt, nur in die andere Richtung. */ + ok(ichHand.darf_zugaenge_fuehren === (handProt.code === 200), + "die Auskunft an die Oberflaeche und die Tuer am Server sagen dasselbe"); + ok(ichLinke.darf_zugaenge_fuehren === (linkeProt.code === 200), + " und bei der linken Hand ebenfalls"); +} + try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } console.log(""); diff --git a/server/workspace-personen.js b/server/workspace-personen.js index bfd045cc..46e70ad1 100644 --- a/server/workspace-personen.js +++ b/server/workspace-personen.js @@ -19,7 +19,7 @@ import express from "express"; naechsten Umzug stehen bleibt. */ import { anmeldeAdresseFuer } from "./crew-adresse.js"; import { - db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtModis, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy, verborgeneIds, TEAM_DOGI_ROLLEN, darfAnlegen, darfRollenWechseln, darfRolleAendern, rollenZumAendern, hausBedingung, istHand, + db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtModis, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy, verborgeneIds, TEAM_DOGI_ROLLEN, darfAnlegen, darfRollenWechseln, darfRolleAendern, rollenZumAendern, hausBedingung, istHand, fuehrtDieZugaenge, } from "./workspace.js"; import { sicherungJetzt } from "./workspace-sicherung.js"; @@ -188,8 +188,32 @@ function nurAdmin(req, res, next) { const vorschauWeg = req.method === "GET" && /^\/workspace\/api\/verwaltung\/personen\/[0-9]+\/loeschbar$/ .test(req.baseUrl + req.path); - if (person.rolle === "hand" - && (anlegeWeg || codeWeg || sperrWeg || loeschWeg || vorschauWeg)) { + /* UND DAS PROTOKOLL (25.09.2026). + + Filipe, mit einem Bildschirmfoto genau dieses Kastens: „die rechte + hand sieht das immer noch nicht obwohl ich will dass die rechte + hand das auch sieht." + + Damit ist auch der letzte Satz von oben ueberholt („Codes, + Sperren, Loeschen, Zuteilung und Protokoll bleiben bei + DogFather"). Seine Ansage vom 24.09. -- „die selben rechte da + haben wie dogfather, das einzige was sie nicht kann ist die + dogfather rolle oder leute anfassen" -- laesst fuer das Protokoll + keinen Rest. + + NUR LESEN: eine Methode (GET), eine Adresse, keine Nummer im Pfad. + Dieselbe Bauweise wie die fuenf Wege darueber. + + `fuehrtDieZugaenge` STATT `person.rolle === "hand"` -- hier stand + der Rollenname, und dieselbe Frage stand in zwei weiteren Dateien + noch einmal. DogFather kommt weiter unten ohnehin durch; dass er + hier mitgemeint ist, aendert nichts und macht die Zeile + wahrheitsfaehig. */ + const protokollWeg = req.method === "GET" + && req.baseUrl + req.path === "/workspace/api/verwaltung/protokoll"; + if (fuehrtDieZugaenge(person) + && (anlegeWeg || codeWeg || sperrWeg || loeschWeg || vorschauWeg + || protokollWeg)) { req.person = person; return next(); } diff --git a/server/workspace.js b/server/workspace.js index f57e9534..b87bce57 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -141,6 +141,42 @@ export const WIE_RECHTE_HAND = new Set(["hand", "linke"]); /** Hat diese Person die Team-Rechte einer Hand (rechts oder links)? */ export const istHand = (person) => !!person && WIE_RECHTE_HAND.has(person.rolle); +/* ===== WER FUEHRT DIE ZUGAENGE? (25.09.2026) ======================== + + Filipe, zum wiederholten Mal: „die rechte hand sieht das immer noch + nicht obwohl ich will dass die rechte hand das auch sieht." Dazu ein + Bildschirmfoto der Personenseite mit den Rollenkarten und „Letzte + Ereignisse". + + NACHGEMESSEN, NICHT GERATEN (mess-hand-personen.mjs): Sie bekam vom + Server alle acht Personen (HTTP 200) -- und sah auf dem Bildschirm + NICHTS davon. Zwei Stellen hielten sie auf, keine davon eine + Sicherung: + + 1. personen.js entschied die Ausbaustufe mit `ich.rolle !== "admin"`. + Ein Rollenname im Browser -- genau die zweite Wahrheit, die in + dieser Datei am 22. und am 24.09. schon zweimal veraltet ist. + 2. personen.css blendet bei `data-nur-anlegen` die Liste aus. Die + Regel stammt vom 07.09. und war fuer Manager und Spicy Media + gedacht; die rechte Hand ist erst danach dazugekommen und fiel + stillschweigend mit hinein. + + Ihr Protokoll war zusaetzlich am Server zu (HTTP 404). + + WARUM ES EINE EIGENE FUNKTION IST UND KEIN ROLLENVERGLEICH: Dieselbe + Frage wird an DREI Stellen gestellt -- die Tuer zum Protokoll, die + Auskunft an die Oberflaeche und die Ausbaustufe der Seite. Drei + Abschriften waeren drei Gelegenheiten, dass die naechste Aenderung + nur zwei davon trifft. Genau so ist dieser Fehler entstanden. + + `istHand` WAERE FALSCH. Es fasst beide Haende zusammen, und fuer die + linke gilt ausdruecklich das Gegenteil: „Kann aber keine Personen + anlegen, keine Rollen aendern und sieht weder Bewerbungen noch den + vertraulichen Meldeweg." Wer hier den Sammelbegriff nimmt, dreht + eine ausgesprochene Entscheidung stillschweigend um. */ +export const fuehrtDieZugaenge = (person) => + !!person && (person.rolle === "admin" || person.rolle === "hand"); + /* ===================================================================== WER EINE KATALOG-VORLAGE BEKOMMEN KANN (24.09.2026) ===================================================================== @@ -6876,6 +6912,12 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => { zuruecknehmen laesst. Heute dieselbe Antwort; sollte Filipe es spaeter wieder einschraenken, ist hier die Stelle. */ darf_personen_loeschen: person?.rolle === "admin" || person?.rolle === "hand", + /* UND OB SIE DIE PERSONENSEITE UEBERHAUPT GANZ SIEHT (25.09.2026). + Liste, Rollenkarten, Protokoll -- siehe `fuehrtDieZugaenge`. Die + Oberflaeche hat das bisher am Rollennamen entschieden und dabei + zwei Tage lang die Liste versteckt, die sie laengst geladen + hatte. */ + darf_zugaenge_fuehren: fuehrtDieZugaenge(person), /* OB DER CHAT-KNOPF IN DER KOPFLEISTE ERSCHEINT (18.09.2026). Er wurde auf JEDER Seite gebaut. Fuer ein Mitglied der Community diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 707d05dc..adc5f77b 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`. --> - - + + - +