From 8cde56bd96f4902527d2a0fecbf58f1e0a700cc2 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Wed, 7 Oct 2026 17:31:11 +0200 Subject: [PATCH] Eine Kampagne gehoert zur Agentur -- auch wenn DogFather ueberall admin ist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aufgefallen beim Nachweis auf der echten Adresse, nicht beim Bauen: Der Kasten haengt an `bereich === "agentur"` und an der Rolle. DogFather ist aber in BEIDEN Haeusern `admin`. Oeffnet er auf crew.dogfather-universe.com von Hand `bereich.html?b=agentur`, dann stimmt der Bereich, die Rolle stimmt -- und `hausFuerNeuenEintrag` gaebe der Kampagne das Haus "crew". Eine Agentur-Kampagne staende im Rudel, und die Trennung waere an genau der Stelle unterlaufen, an der niemand nachsieht. Bauplan, Teil 11: "Nichts im Haus Team Dogi." ZWEI FRAGEN, ZWEI SCHWELLEN -- und das ist Absicht: /api/ich (ZEIGEN) haus !== "crew" Route (SCHREIBEN) haus === "agentur" Der Unterschied betrifft genau einen Fall: `null`. Den gibt es nur auf einer PRUEFADRESSE -- `sitzungLesen` vergibt dort seit dem 24.09.2026 bewusst kein Haus, und die Begruendung dort nennt den Schaden: Als 127.0.0.1 einmal ein Haus bekam, fiel pruef-chat-kanaele mit 13 Fehlschlaegen um, und die naechsten Pruefungen waeren still gruen geblieben, ohne noch etwas zu messen. Eine echte Anfrage hat immer eine der beiden Waende; eine Sitzung zu einer dritten lehnt `sitzungPasstZurAdresse` ohnehin ab. Beim SCHREIBEN wird trotzdem streng gefragt: Ein Eintrag mit `haus = null` waere in BEIDEN Haeusern sichtbar -- genau das, was Teil 11 ausschliesst. WAS ICH DABEI ZWEIMAL FALSCH VERSUCHT HABE Zuerst sollte das Bildwerkzeug ueber die Agenturwand kommen, damit es den Kasten weiter messen kann. Den Host-Kopf beim Durchreichen zu ueberschreiben wirkt nicht -- gemessen: /api/ich meldete weiter `darf: false`, der Wirt blieb "127.0.0.1:4338". Chromium laesst `Host` nicht ueberschreiben. Dann den Namen wirklich aufloesen (--host-resolver-rules). Das funktioniert, bringt aber die Anmeldemaske der echten Wand mit einer Tuer davor, die ein Bildwerkzeug nicht aufmachen soll. Gemessen werden soll die KACHEL, nicht der Zugang. Deshalb bleibt bild-kampagne auf der Pruefadresse -- und genau deshalb fragt /api/ich `!== "crew"` statt `=== "agentur"`. Beide Zeilen tragen jetzt die Begruendung bei sich, damit sie niemand "vereinheitlicht": Das macht entweder die Pruefungen blind oder laesst eine Kampagne ins Rudel. Zwei Gegenproben gefahren, beide schlagen an: Route nimmt jede Wand an -> 2 rot. /api/ich fragt nicht nach dem Haus -> 1 rot. GEPRUEFT: pruef-kampagne 98 -> 99 gruen · bild-kampagne 20 Messungen gruen · pruef-struktur, pruef-haus-trennung 107, pruef-agentur 62, pruef-eventkarte 94, pruef-alle-wege, pruef-schranke, pruef-zeichen 8 Co-Authored-By: Claude Opus 5 --- server/bild-kampagne.mjs | 39 +++++++++++++++++++++++++++++++++-- server/pruef-kampagne.mjs | 21 +++++++++++++++++++ server/workspace-kampagne.js | 40 +++++++++++++++++++++++++++++++----- server/workspace.js | 30 ++++++++++++++++++++++++++- 4 files changed, 122 insertions(+), 8 deletions(-) diff --git a/server/bild-kampagne.mjs b/server/bild-kampagne.mjs index 59992010..277b9c3e 100644 --- a/server/bild-kampagne.mjs +++ b/server/bild-kampagne.mjs @@ -37,7 +37,24 @@ express.response.cookie = function (n, w, o) { }; await import("./index.js"); await new Promise((r) => setTimeout(r, 1200)); -const BASIS = `http://127.0.0.1:${PORT}`; +/* WARUM HIER 127.0.0.1 STEHT UND NICHT DIE AGENTURWAND + (gemessen und wieder zurueckgebaut, 07.10.2026). + + Versucht wurde beides. Der Host-Kopf laesst sich in Chromium + nicht ueberschreiben (gemessen: /api/ich meldete weiter + `darf: false`, der Wirt blieb "127.0.0.1"), und laesst man den + Namen wirklich aufloesen, kommt die Anmeldemaske der echten Wand + -- mit einer Tuer davor, die dieses Werkzeug nicht aufmachen + soll. Gemessen werden soll die KACHEL, nicht der Zugang. + + Deshalb bleibt es bei der Pruefadresse. Dort vergibt + `sitzungLesen` bewusst KEIN Haus (24.09.2026), und `/api/ich` + fragt genau deshalb `haus !== "crew"` statt `=== "agentur"`: + Der Kasten ist hier sichtbar und damit messbar. + + Dass er auf der CREW-Adresse verschwindet, misst + pruef-kampagne mit echten Host-Koepfen -- das geht ohne + Browser und ist dort besser aufgehoben. */const BASIS = `http://127.0.0.1:${PORT}`; const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes } = await import("node:crypto"); @@ -65,11 +82,23 @@ const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_mo const browser = await pw.chromium.launch(); const fehlerAufSeite = []; + const anmelden = async (seite, rolle, code) => { await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" }); await seite.click(`.rolle[data-rolle="${rolle}"]`); await seite.fill("#code", code); - await seite.click("button[type=submit]"); + /* DEN KNOPF DIREKT AUSLOESEN, nicht ueber die Maus. + + page.click wartet darauf, dass das Element "visible, enabled and + stable" ist -- und auf der echten Agenturwand lief es dort in die + 30-Sekunden-Frist. Die Anmeldeseite bewegt sich dauerhaft (die + Buehne atmet), also wird "stable" nie erreicht. Auf 127.0.0.1 war + das nie aufgefallen, weil dort eine andere Szene laeuft. + + Gemessen wird hier die KACHEL, nicht die Anmeldemaske -- der + direkte Klick ist deshalb kein Umgehen einer Pruefung, sondern + der Weg zum eigentlichen Messgegenstand. */ + await seite.$eval("button[type=submit]", (b) => b.click()); await seite.waitForURL("**/start.html", { timeout: 25000 }); }; @@ -107,6 +136,12 @@ async function messen(breite, hoehe, name) { await seite.waitForTimeout(1600); console.log(`\n=== ${name} (${breite} px) ===`); + const ichSagt = await seite.evaluate(async () => { + const a = await fetch("/workspace/api/ich", { credentials: "same-origin" }); + const j = await a.json().catch(() => ({})); + return { rolle: j.rolle, darf: j.darf_kampagne_einlesen, wirt: location.host }; + }); + console.log(" /api/ich: " + JSON.stringify(ichSagt)); const mass = await seite.evaluate(() => { const k = document.getElementById("kampagneholen"); if (!k || k.hidden) return { da: false }; diff --git a/server/pruef-kampagne.mjs b/server/pruef-kampagne.mjs index ee848d57..0386a2c2 100644 --- a/server/pruef-kampagne.mjs +++ b/server/pruef-kampagne.mjs @@ -753,6 +753,15 @@ melde("=== 11. Kampagnen anlegen dürfen DogFather und die Manager ==="); && ichLuna.darf_kampagne_einlesen === false, "/api/ich sagt der Oberfläche dasselbe wie die Schranke der Route"); + /* UND AUF DER CREW-ADRESSE SAGT SIE NEIN. Das ist der Fall, den + es in der Wirklichkeit gibt: DogFather ist dort ebenfalls + `admin`, und ohne diese Antwort stuende der Kasten im Rudel. */ + const ichCrew = JSON.parse((await roh("/workspace/api/ich", + { keks: keksDogiCrew, wirt: CREW_HOST })).text); + ok(ichCrew.rolle === "admin" && ichCrew.darf_kampagne_einlesen === false, + `auf der Crew-Adresse sagt sie nein (Rolle ${ichCrew.rolle},` + + ` darf ${ichCrew.darf_kampagne_einlesen})`); + /* FREMDE HERKUNFT. Die Wache steht in index.js, VOR diesem Router. */ const fremd = await new Promise((f, s) => { const daten = Buffer.from(JSON.stringify({ text: "x" })); @@ -795,6 +804,18 @@ melde("=== 12. Team Dogi bleibt unberührt ==="); ok((wsBrett.eintraege || []).some((e) => e.id === kachelId), "Gegenprobe: auf der Agentur-Adresse schon"); + /* UND VON DER CREW-ADRESSE AUS LAESST SIE SICH GAR NICHT ANLEGEN. + + DogFather ist in beiden Haeusern `admin` -- die Rollenfrage + allein laesst ihn also durch. Ohne diese Schranke bekaeme die + Kampagne das Haus "crew", und die Trennung waere an genau der + Stelle unterlaufen, an der niemand nachsieht. */ + const vonCrew = await einlesen(`Klick: ${TIK}/t/gipfel`, keksDogiCrew, CREW_HOST); + ok(vonCrew.code === 403, + `von der Crew-Adresse: HTTP ${vonCrew.code} — „${vonCrew.j.fehler}“`); + ok(/[Aa]gentur/.test(vonCrew.j.fehler || ""), + " und die Meldung sagt, wo es hingehoert"); + const crewNachher = dbLesen("SELECT COUNT(*) AS n FROM eintraege WHERE haus = 'crew'")[0].n; ok(crewVorher === crewNachher, `und die Zahl der Crew-Einträge ist unverändert (${crewVorher} = ${crewNachher})`); diff --git a/server/workspace-kampagne.js b/server/workspace-kampagne.js index e79a0e8f..1e280d38 100644 --- a/server/workspace-kampagne.js +++ b/server/workspace-kampagne.js @@ -264,6 +264,36 @@ kampagneRouter.post("/workspace/api/kampagne/einlesen", async (req, res) => { `keine Leitung (${person.rolle})`); } + /* ---- Und nur auf der AGENTURADRESSE --------------------- + + Bauplan, Teil 11: "Nichts im Haus Team Dogi." + + WARUM DAS EINE EIGENE ZEILE BRAUCHT: DogFather ist in BEIDEN + Haeusern `admin`, und `istLeitung` fragt nur nach der Rolle. + Meldet er sich auf crew.dogfather-universe.com an und oeffnet + dort `bereich.html?b=agentur`, dann ist er Leitung, der + Bereich stimmt -- und `hausFuerNeuenEintrag` gaebe der + Kampagne das Haus "crew". Eine Agentur-Kampagne staende + damit im Rudel, und die Trennung waere genau an der Stelle + unterlaufen, an der sie niemand nachsieht. + + Gefragt wird POSITIV ("ist es die Agentur?") und nicht + negativ ("ist es nicht die Crew?"): Ohne Adresse gibt + `hausFuerNeuenEintrag` `null` zurueck, und ein Eintrag ohne + Haus ist in BEIDEN sichtbar. Fuer die meisten Eintraege ist + das die bewusst sichere Richtung -- fuer eine Kampagne waere + es das Gegenteil. + + Dasselbe Haus wird unten beim Schreiben benutzt, nicht ein + zweites Mal berechnet. */ + const haus = hausFuerNeuenEintrag(person, "agentur"); + if (haus !== "agentur") { + return schiefgegangen(403, + "Kampagnen gehören zur Agentur. Bitte auf " + + "workspace.dogfather-universe.com anmelden.", + `falsches Haus: ${haus ?? "ohne Adresse"}`); + } + const text = String(req.body?.text ?? ""); if (!text.trim()) { return schiefgegangen(400, @@ -440,11 +470,11 @@ kampagneRouter.post("/workspace/api/kampagne/einlesen", async (req, res) => { kuerzen(regelnText(gelesen.kuer, hinweise)), dringlichkeit, jetzt, person.id, - /* NICHT von Hand auf "agentur" gesetzt: Das Haus entscheidet - die Adresse, über die jemand hereinkommt. Wer es hier - festschreibt, baut die Trennung ein zweites Mal -- und - zwei Fassungen derselben Regel laufen auseinander. */ - hausFuerNeuenEintrag(person, "agentur"), + /* NICHT von Hand auf "agentur" gesetzt, sondern dieselbe + Angabe, die oben schon ueber die Annahme entschieden + hat. Zweimal zu rechnen waeren zwei Fassungen derselben + Regel -- und die laufen auseinander. */ + haus, nummer, link, jetzt, gelesen.pflicht.zone, JSON.stringify(gelesen.auszug)); eintragId = Number(lastInsertRowid); diff --git a/server/workspace.js b/server/workspace.js index 59f07fe9..ddbdd31a 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -7680,7 +7680,35 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => { obwohl der Weg dahinter offen war. Und die umgekehrte Richtung ist schlimmer: Ein Feld, das erscheint und dann 403 bekommt, laesst den Menschen davor sich selbst fuer den Fehler halten. */ - darf_kampagne_einlesen: istLeitung(person), + /* UND NICHT IM HAUS TEAM DOGI (nachgetragen 07.10.2026). + + Hier stand zuerst nur `istLeitung(person)`. DogFather ist + aber in BEIDEN Haeusern `admin`: Auf der Crew-Adresse haette + die Oberflaeche den Kasten gezeigt, und der Server haette mit + 403 abgelehnt. Ein Formular, das der Server ablehnt, laesst + den Menschen davor sich selbst fuer den Fehler halten. + + ZWEI FRAGEN, ZWEI SCHWELLEN -- und das ist Absicht: + + hier (ZEIGEN) haus !== "crew" + Route (SCHREIBEN) haus === "agentur" + + Der Unterschied betrifft genau einen Fall: `null`. Den gibt + es nur auf einer PRUEFADRESSE -- `sitzungLesen` vergibt dort + bewusst kein Haus (siehe die Begruendung dort, 24.09.2026: + Als 127.0.0.1 einmal ein Haus bekam, fiel pruef-chat-kanaele + mit 13 Fehlschlaegen um, und die naechsten Pruefungen waeren + still gruen geblieben, ohne noch etwas zu messen). Eine + echte Anfrage hat immer eine der beiden Waende; eine Sitzung + zu einer dritten lehnt `sitzungPasstZurAdresse` ohnehin ab. + + Beim SCHREIBEN wird trotzdem streng gefragt: Ein Eintrag mit + `haus = null` waere in BEIDEN Haeusern sichtbar -- genau das, + was Teil 11 des Bauplans ausschliesst. Wer die beiden Zeilen + einmal "vereinheitlicht", macht entweder die Pruefungen + blind oder laesst eine Kampagne ins Rudel. */ + darf_kampagne_einlesen: istLeitung(person) + && hausFuerNeuenEintrag(person, "agentur") !== "crew", /* ZWEI LISTEN STATT EINER RECHNUNG IM BROWSER (20.09.2026). Die Rollenwahl nahm bisher die Knoepfe des Anlege-Formulars --