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 <[email protected]>
454 lines
22 KiB
JavaScript
454 lines
22 KiB
JavaScript
/* =====================================================================
|
|
DIE RECHTE HAND AUF DER PERSONENSEITE (24.09.2026)
|
|
|
|
Filipe: „die rechte hand soll das auch sehen. und die selben rechte
|
|
da haben wie dogfather. das einzige was sie nicht kann ist die
|
|
dogfather rolle oder leute anfassen. also da kann sie nichts
|
|
verändern."
|
|
|
|
WAS HIER GEPRUEFT WIRD, und warum jede Zeile:
|
|
|
|
* SIE DARF, was DogFather darf -- anlegen, Code, Rolle, sperren,
|
|
loeschen. Bis heute war die Oberflaeche strenger als der Server:
|
|
Sie hatte drei dieser Rechte und sah keinen einzigen Knopf.
|
|
* SIE DARF NICHT an DogFather und an die Manager. Das ist die
|
|
Grenze aus seinem Satz, und sie wird EINZELN geprueft -- fuer
|
|
jeden der fuenf Wege.
|
|
* DER LETZTE DOGFATHER-ZUGANG bleibt. Diese Sicherung stand
|
|
vorher hinter „nur DogFather darf loeschen"; seit die rechte
|
|
Hand loescht, muss sie allein tragen.
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-hp-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
const { eigenerPort } = await import("./helfer-port.mjs");
|
|
const PORT = await eigenerPort(import.meta, "die Hand-Personen-Pruefung");
|
|
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
|
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 }); };
|
|
await import("./index.js");
|
|
notbremse(240_000, "pruef-hand-personen");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
const melde = (t) => console.log(t);
|
|
|
|
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 ks = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", ks, 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,"
|
|
+ " code_kennung, aktiv, erstellt) VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768,
|
|
createHmac("sha256", ks).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
const idHand = anlegen("VanVan", "hand", "CODE-HAND-0001");
|
|
const idLinke = anlegen("Funny", "linke", "CODE-LINK-0001");
|
|
const idModi = anlegen("Miss", "modi", "CODE-MODI-0001");
|
|
const idModi2 = anlegen("Ghost", "modi", "CODE-MODI-0002");
|
|
const idMana = anlegen("Patrick", "manager", "CODE-MANA-0001");
|
|
const idGast = anlegen("Kessi", "gast", "CODE-GAST-0001");
|
|
d.close();
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const { request } = await import("node:http");
|
|
|
|
/** Ein Aufruf mit Hausadresse -- `fetch` verwirft den Host-Kopf. */
|
|
function roh(pfad, keks, koerper, methode) {
|
|
return new Promise((fertig, schief) => {
|
|
const daten = koerper ? JSON.stringify(koerper) : null;
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: pfad,
|
|
method: methode || (daten ? "POST" : "GET"),
|
|
headers: {
|
|
Host: CREW, Origin: `https://${CREW}`,
|
|
...(keks ? { Cookie: keks } : {}),
|
|
...(daten ? { "Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(daten) } : {}),
|
|
},
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text }));
|
|
});
|
|
a.on("error", schief);
|
|
if (daten) a.write(daten);
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (r) => { try { return JSON.parse(r.text); } catch { return {}; } };
|
|
|
|
async function anmelden(rolle, code) {
|
|
const r = await new Promise((fertig, schief) => {
|
|
const daten = JSON.stringify({ rolle, code, alter_ok: true });
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden",
|
|
method: "POST",
|
|
headers: { Host: CREW, Origin: `https://${CREW}`,
|
|
"Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
a.write(daten); a.end();
|
|
});
|
|
if (r.code !== 200) return null;
|
|
return [].concat(r.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001");
|
|
const kHand = await anmelden("hand", "CODE-HAND-0001");
|
|
const kLinke = await anmelden("linke", "CODE-LINK-0001");
|
|
const kModi = await anmelden("modi", "CODE-MODI-0001");
|
|
|
|
melde("\n=== 0. Alle vier kommen herein ===");
|
|
ok(!!kDogi && !!kHand && !!kLinke && !!kModi, "DogFather, rechte Hand, linke Hand, Modi");
|
|
|
|
/* =====================================================================
|
|
1. WAS DIE OBERFLAECHE ERFAEHRT
|
|
===================================================================== */
|
|
melde("\n=== 1. Die Auskunft an die Oberflaeche ===");
|
|
{
|
|
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", kLinke));
|
|
const ichModi = json(await roh("/workspace/api/ich", kModi));
|
|
|
|
ok(ichDogi.darf_zugaenge_verwalten === true, "DogFather verwaltet Zugaenge");
|
|
ok(ichHand.darf_zugaenge_verwalten === true, "die rechte Hand auch");
|
|
ok(ichLinke.darf_zugaenge_verwalten === false,
|
|
"die LINKE Hand nicht -- das war eine eigene Entscheidung vom 21.09.");
|
|
ok(ichModi.darf_zugaenge_verwalten !== true, "ein Modi erst recht nicht");
|
|
|
|
ok(ichHand.darf_personen_loeschen === true, "sie darf loeschen");
|
|
ok(ichLinke.darf_personen_loeschen !== true, "die linke Hand nicht");
|
|
|
|
/* WEN sie anfassen darf -- die Grenze aus Filipes Satz. */
|
|
const anf = ichHand.rollen_anfassbar || [];
|
|
ok(!anf.includes("admin"), `DogFather steht NICHT darin (${anf.join(", ")})`);
|
|
/* DIE MANAGER STEHEN IN `rollen_anfassbar` -- und das ist KEIN
|
|
Mangel, sondern die falsche Frage. `rollen_anfassbar` sagt, welche
|
|
Rollen sie grundsaetzlich aendern duerfte; ob ein bestimmter
|
|
Mensch fuer sie existiert, entscheidet die Haustrennung: Auf
|
|
crew. liefert `hausBedingung` nur Team-Dogi-Rollen, ein Manager
|
|
ist dort fuer NIEMANDEN sichtbar, auch nicht fuer DogFather.
|
|
Gemessen wird das in Abschnitt 3 an den echten Wegen. */
|
|
ok(Array.isArray(anf), `rollen_anfassbar ist eine Liste (${anf.join(", ")})`);
|
|
ok(anf.includes("modi") || anf.includes("gast"),
|
|
"Modis und Community schon -- sonst waere das Recht leer");
|
|
}
|
|
|
|
/* =====================================================================
|
|
2. WAS SIE DARF
|
|
===================================================================== */
|
|
melde("\n=== 2. Was sie darf ===");
|
|
let neuerModi = 0;
|
|
{
|
|
const anlegen2 = await roh("/workspace/api/verwaltung/personen", kHand,
|
|
{ name: "Neue Modi", rolle: "modi" });
|
|
ok(anlegen2.code === 200 || anlegen2.code === 201,
|
|
`sie legt einen Modi an (${anlegen2.code})`);
|
|
neuerModi = json(anlegen2).id || 0;
|
|
ok(!!json(anlegen2).code, "und sieht den Code genau einmal");
|
|
|
|
const code = await roh(`/workspace/api/verwaltung/personen/${idModi}/code`, kHand, {}, "POST");
|
|
ok(code.code === 200, `sie erzeugt einen Code neu (${code.code})`);
|
|
|
|
const rolle = await roh(`/workspace/api/verwaltung/personen/${idModi2}/rolle`, kHand,
|
|
{ rolle: "gast" }, "PUT");
|
|
ok(rolle.code === 200, `sie aendert eine Rolle (${rolle.code})`);
|
|
|
|
const sperren = await roh(`/workspace/api/verwaltung/personen/${idModi}`, kHand,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(sperren.code === 200, `sie sperrt (${sperren.code}) -- das ist neu seit heute`);
|
|
|
|
const entsperren = await roh(`/workspace/api/verwaltung/personen/${idModi}`, kHand,
|
|
{ aktiv: true }, "PATCH");
|
|
ok(entsperren.code === 200, `und entsperrt wieder (${entsperren.code})`);
|
|
|
|
if (neuerModi) {
|
|
const vorschau = await roh(`/workspace/api/verwaltung/personen/${neuerModi}/loeschbar`, kHand);
|
|
ok(vorschau.code === 200, `sie sieht die Loesch-Vorschau (${vorschau.code})`);
|
|
const weg = await roh(`/workspace/api/verwaltung/personen/${neuerModi}`, kHand,
|
|
{ name: "Neue Modi" }, "DELETE");
|
|
ok(weg.code === 200, `sie loescht (${weg.code}) -- das ist neu seit heute`);
|
|
} else {
|
|
ok(false, "kein angelegter Modi zum Loeschen");
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
3. WAS SIE NICHT DARF -- jeden Weg einzeln
|
|
===================================================================== */
|
|
melde("\n=== 3. An DogFather und den Managern nicht ===");
|
|
{
|
|
/* GEPRUEFT WIRD MIT TEAM-DOGI-ROLLEN. Ein Manager gehoert zur
|
|
Agentur und ist auf crew. fuer niemanden sichtbar -- ihn hier zu
|
|
nehmen haette 404 gemessen und „geschuetzt" genannt, obwohl es
|
|
nur die Haustrennung war. Die linke Hand ist der richtige Fall:
|
|
Sie IST auf dieser Adresse, und die rechte Hand darf sie
|
|
trotzdem nicht anfassen. */
|
|
for (const [wen, id, name] of [
|
|
["DogFather", idDogi, "Filipe"], ["die linke Hand", idLinke, "Funny"]]) {
|
|
const code = await roh(`/workspace/api/verwaltung/personen/${id}/code`, kHand, {}, "POST");
|
|
ok(code.code === 403 || code.code === 404,
|
|
`kein neuer Code fuer ${wen} (${code.code})`);
|
|
|
|
const rolle = await roh(`/workspace/api/verwaltung/personen/${id}/rolle`, kHand,
|
|
{ rolle: "modi" }, "PUT");
|
|
ok(rolle.code === 403 || rolle.code === 404,
|
|
`keine Rollenaenderung bei ${wen} (${rolle.code})`);
|
|
|
|
const sperren = await roh(`/workspace/api/verwaltung/personen/${id}`, kHand,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(sperren.code === 403 || sperren.code === 404,
|
|
`kein Sperren von ${wen} (${sperren.code})`);
|
|
|
|
const weg = await roh(`/workspace/api/verwaltung/personen/${id}`, kHand,
|
|
{ name }, "DELETE");
|
|
ok(weg.code === 403 || weg.code === 404,
|
|
`kein Loeschen von ${wen} (${weg.code})`);
|
|
|
|
const vorschau = await roh(`/workspace/api/verwaltung/personen/${id}/loeschbar`, kHand);
|
|
ok(vorschau.code === 403 || vorschau.code === 404,
|
|
`nicht einmal die Loesch-Vorschau von ${wen} (${vorschau.code})`);
|
|
}
|
|
|
|
/* UND DIE MANAGER -- hier zeigt sich der Unterschied am deutlichsten.
|
|
|
|
Mein erster Anlauf erwartete, dass BEIDE abgewiesen werden ("auf
|
|
crew. gibt es keine Manager"). Gemessen: Hand 403, DogFather 200.
|
|
Das war kein Loch, sondern genau die bestellte Ordnung -- die
|
|
Haustrennung gilt fuer die LISTE, nicht fuer den direkten Zugriff
|
|
von DogFather. Ich hatte das eine fuer das andere gehalten.
|
|
|
|
Beide Zeilen stehen jetzt getrennt da, und gerade ihr Unterschied
|
|
ist die Aussage: Derselbe Weg, zwei Rollen, zwei Antworten. */
|
|
const handMana = await roh(`/workspace/api/verwaltung/personen/${idMana}`, kHand,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(handMana.code === 403 || handMana.code === 404,
|
|
`die rechte Hand kommt an einen Manager nicht heran (${handMana.code})`);
|
|
const dogiMana = await roh(`/workspace/api/verwaltung/personen/${idMana}`, kDogi,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(dogiMana.code === 200,
|
|
`DogFather schon (${dogiMana.code}) -- sonst hiesse "sie darf es nicht" nur, `
|
|
+ `dass es niemand darf`);
|
|
/* Wieder entsperren, damit die Schlussprobe den Grundstand sieht. */
|
|
await roh(`/workspace/api/verwaltung/personen/${idMana}`, kDogi, { aktiv: true }, "PATCH");
|
|
|
|
/* SIE KANN KEINEN DOGFATHER ERSCHAFFEN. Die Rolle ist fuer niemanden
|
|
vergebbar, auch nicht fuer DogFather selbst -- hier zaehlt, dass
|
|
der Weg ueber sie nicht offensteht. */
|
|
const machDogi = await roh(`/workspace/api/verwaltung/personen/${idModi}/rolle`, kHand,
|
|
{ rolle: "admin" }, "PUT");
|
|
ok(machDogi.code >= 400, `sie macht niemanden zum DogFather (${machDogi.code})`);
|
|
|
|
const anlegeDogi = await roh("/workspace/api/verwaltung/personen", kHand,
|
|
{ name: "Zweiter Chef", rolle: "admin" });
|
|
ok(anlegeDogi.code >= 400, `und legt keinen an (${anlegeDogi.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
4. DIE ANDEREN ROLLEN KOMMEN GAR NICHT HERAN
|
|
===================================================================== */
|
|
melde("\n=== 4. Alle anderen ===");
|
|
{
|
|
for (const [wer, keks] of [["die linke Hand", kLinke], ["ein Modi", kModi]]) {
|
|
const anl = await roh("/workspace/api/verwaltung/personen", keks,
|
|
{ name: "Versuch", rolle: "modi" });
|
|
ok(anl.code >= 400, `${wer} legt niemanden an (${anl.code})`);
|
|
const weg = await roh(`/workspace/api/verwaltung/personen/${idGast}`, keks,
|
|
{ name: "Kessi" }, "DELETE");
|
|
ok(weg.code >= 400, `${wer} loescht niemanden (${weg.code})`);
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
5. GEGENPROBEN -- kann das hier ueberhaupt rot werden?
|
|
===================================================================== */
|
|
melde("\n=== 5. Gegenproben (jede MUSS anschlagen) ===");
|
|
{
|
|
/* DogFather selbst kann alles -- sonst hiesse „sie darf dasselbe"
|
|
nur, dass beide nichts duerfen. */
|
|
const dogiCode = await roh(`/workspace/api/verwaltung/personen/${idLinke}/code`, kDogi, {}, "POST");
|
|
ok(dogiCode.code === 200,
|
|
`DogFather erzeugt einen Code fuer die linke Hand (${dogiCode.code}) -- `
|
|
+ `sie kann es nicht, er schon`);
|
|
|
|
/* DER LETZTE DOGFATHER-ZUGANG BLEIBT -- auch fuer ihn selbst.
|
|
Diese Sicherung stand vorher hinter „nur DogFather darf
|
|
loeschen"; jetzt traegt sie allein. */
|
|
const letzter = await roh(`/workspace/api/verwaltung/personen/${idDogi}`, kDogi,
|
|
{ name: "Filipe" }, "DELETE");
|
|
ok(letzter.code >= 400,
|
|
`der letzte DogFather-Zugang laesst sich nicht loeschen (${letzter.code})`);
|
|
|
|
/* Ohne Anmeldung gar nichts. */
|
|
const ohne = await roh("/workspace/api/verwaltung/personen", null,
|
|
{ name: "Fremd", rolle: "modi" });
|
|
ok(ohne.code >= 400, `ohne Anmeldung nichts (${ohne.code})`);
|
|
|
|
/* UND DIE TAFEL IST DANACH UNVERSEHRT: DogFather, Manager und die
|
|
linke Hand stehen noch da. Ein abgewiesener Versuch, der trotzdem
|
|
etwas aendert, waere der schlimmste Ausgang. */
|
|
const liste = json(await roh("/workspace/api/verwaltung/personen", kDogi));
|
|
const namen = (liste.personen || []).map((x) => x.rolle);
|
|
/* Ohne "manager" -- der ist auf crew. gar nicht in der Liste, siehe
|
|
oben. Ihn hier zu erwarten hiesse, die Haustrennung fuer einen
|
|
Fehler zu halten. */
|
|
ok(namen.includes("admin") && namen.includes("linke") && namen.includes("hand"),
|
|
`nach allen Versuchen stehen sie noch da (${namen.length} Personen: `
|
|
+ `${[...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("");
|
|
console.log(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
console.log(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
process.exit(fehler ? 1 : 0);
|