Files
dogfather-universe/server/pruef-hand-personen.mjs
DogFatherGitandClaude Opus 5 63a3fb4af8 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 <[email protected]>
2026-09-25 02:47:07 +02:00

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);