DogFather konnte einen zweiten DogFather nicht mehr herabstufen

Gefunden beim Durchsehen der schnellen Pruefungen: pruef-haus-trennung
stand auf 60 ok / 6 Fehler. Drei Ursachen, eine davon ein echter
Rueckschritt von gestern.

--- 1. DER RUECKSCHRITT ---------------------------------------------

In darfRolleAendern stand seit gestern:

  if (person.rolle === "admin") return ziel.rolle !== "admin";

Das macht aus "DogFather ist nicht mehr VERGEBBAR" ein "an einem
DogFather ist nichts mehr zu aendern". Ein zweiter Zugang liess sich
damit nie wieder zuruecksetzen -- er waere fuer immer DogFather
geblieben. Die Pruefung sagte es woertlich: "solange es zwei gibt,
darf einer wechseln (403)".

Die beiden echten Gefahren haengen woanders und bleiben unberuehrt:
Niemand KANN 'admin' vergeben, und der LETZTE DogFather laesst sich
nicht herabstufen.

--- 2. ZWEI SCHLOESSER FUER DIESELBE TUER ---------------------------

Ich hatte gestern `rollenZumAendern` mit 403 VOR die vorhandene
Pruefung gesetzt, die mit 400 "Unbekannte Rolle." antwortet. Damit war
die Luecke wieder offen, vor der der Kommentar vom 17.09. direkt
daneben warnt: Zwei verschiedene Antworten -- "gibt es nicht" gegen
"darfst du nicht" -- verraten beim Durchprobieren, WELCHE Rollen
existieren.

Jetzt eine Regel an einer Stelle. `darfAnlegen` ist dort raus, weil
Vergeben und Anlegen seit gestern verschiedene Dinge sind. Gemessen:

  admin   anlegen = aendern (sieben Rollen)
  spicy   anlegen = aendern (vier)
  hand    anlegen = —        aendern = modi, gast
  manager anlegen = creator  aendern = —

Und die eigene Zeile bekommt jetzt 400 mit einem Satz statt 403:
Es ist keine Rechtefrage, sondern eine unsinnige Bitte.

--- 3. DREI ERWARTUNGEN, DIE AELTER WAREN ALS DIE REGEL --------------

Die Pruefung verlangte 403, wo seit dem 17.09. 400 richtig ist --
ihre Zeile stammt vom 10.09., sieben Tage aelter als die Regel. Und
sie verlangte 404 fuer "die rechte Hand vergibt keine Rolle", was bis
vorgestern stimmte.

Dabei fiel auf, dass NICHTS geprueft hat, ob die rechte Hand ihre neue
Faehigkeit ueberhaupt ausueben kann -- nur, dass sie es nicht darf.
Eine Pruefung, die nur das Verbotene misst, laesst offen, ob das
Erlaubte geht. Jetzt beide Richtungen:

  an einer anderen rechten Hand aendert sie nichts (403)
  und an DogFather erst recht nicht (403)
  einen Modi macht sie sehr wohl zur Community (200)
  und es steht so in der Datenbank (gast)
  eine zweite rechte Hand ernennt sie nicht (400)

Nebenbei: `unbekannte_kachel` (gestern eingefuehrt) hatte keinen Satz
in meldung.js -- die Kennung waere woertlich auf dem Bildschirm
gelandet. pruef-meldungen hatte es gemeldet.

Und ein Absturz beim Bauen: `const anlegen = await hole(...)` weiter
unten im selben Block verdeckt die gleichnamige Funktion im GANZEN
Block, auch oberhalb seiner eigenen Zeile.

pruef-haus-trennung: 70 ok, 0 Fehler (vorher 60/6).
Dazu gruen: rollen-anlegen 11/0, personen-loeschen 40/0,
rechte-umstellen 46/0, verteilen 11/0, meldungen 8/0.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-20 23:13:27 +02:00
co-authored by Claude Opus 5
parent 445ea88f2b
commit 67a0eb8717
4 changed files with 114 additions and 9 deletions
+61 -4
View File
@@ -420,7 +420,13 @@ console.log("=== Personen & Zugaenge ===");
const neuCrew = await hole("/workspace/api/verwaltung/personen", { host: CREW,
methode: "POST", keks: dogi.keks,
rumpf: JSON.stringify({ name: "Testcreator", rolle: "creator" }) });
ok(neuCrew.status === 403,
/* 400, NICHT 403 (berichtigt 20.09.2026). Diese Erwartung stammt vom
10.09.; am 17.09. wurde entschieden, dass eine abgelehnte Rolle
IMMER "Unbekannte Rolle." mit 400 bekommt -- egal ob es sie nicht
gibt oder ob sie hier nur nicht erlaubt ist. Zwei verschiedene
Antworten verraten beim Durchprobieren, WELCHE Rollen existieren.
Die Pruefung war sieben Tage aelter als die Regel. */
ok(neuCrew.status === 400,
`ein Creator laesst sich auf crew. nicht anlegen (${neuCrew.status})`);
/* GEGENPROBE: Auf der Agenturadresse geht genau das. Ohne sie hiesse
die 403 oben auch dann gruen, wenn Anlegen ueberhaupt kaputt waere. */
@@ -499,7 +505,8 @@ console.log("=== Die Rolle aendern ===");
Rollen kommen aus derselben Auskunft wie beim Anlegen. */
const fremd = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT",
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "creator" }) });
ok(fremd.status === 403, `auf crew. auch nicht zum Creator (${fremd.status})`);
/* Auch hier 400 statt 403 -- gleiche Begruendung wie oben. */
ok(fremd.status === 400, `auf crew. auch nicht zum Creator (${fremd.status})`);
/* SEIT DEM 11.09.2026 LAESST SICH DIE ROLLE NICHT MEHR VERGEBEN.
@@ -624,9 +631,59 @@ console.log("=== Alle Kategorien ===");
const codeVersuch = await hole(`/workspace/api/verwaltung/personen/${idModi}/code`,
{ host: CREW, methode: "POST", keks: hand.keks, rumpf: "{}" });
ok(codeVersuch.status === 404, `sie kann keinen Code neu setzen (${codeVersuch.status})`);
const rolleVersuch = await hole(`/workspace/api/verwaltung/personen/${idModi}/rolle`,
/* ROLLEN WECHSELN DARF SIE SEIT DEM 20.09. -- aber nicht alles.
Filipe: "ich will dass ich da auch die rollen der leute wechseln
kann ohne dass ich denen einen neuen account machen muss ...
perfektionier das fuer die rolle dogfather und rechte hand. nur
die sollen das machen koennen."
Hier stand `=== 404` ("sie kann gar nichts"). Das war bis zum
19.09. richtig. Jetzt werden BEIDE Richtungen gemessen -- eine
Pruefung, die nur das Verbotene misst, laesst den Fall offen, dass
auch das Erlaubte nicht geht, und genau das faellt niemandem auf,
bis sich jemand beschwert.
Ayla ist an dieser Stelle laengst zur rechten Hand befoerdert
(Abschnitt weiter oben) -- deshalb ist sie der Fall "nicht unter
sich". */
const anEinerHand = await hole(`/workspace/api/verwaltung/personen/${idModi}/rolle`,
{ host: CREW, methode: "PUT", keks: hand.keks, rumpf: JSON.stringify({ rolle: "modi" }) });
ok(rolleVersuch.status === 404, `und keine Rolle vergeben (${rolleVersuch.status})`);
ok(anEinerHand.status === 403,
`an einer anderen rechten Hand aendert sie nichts (${anEinerHand.status})`);
const anDogFather = await hole(`/workspace/api/verwaltung/personen/${idDogi}/rolle`,
{ host: CREW, methode: "PUT", keks: hand.keks, rumpf: JSON.stringify({ rolle: "modi" }) });
ok(anDogFather.status === 403,
`und an DogFather erst recht nicht (${anDogFather.status})`);
/* UND DIE ANDERE RICHTUNG: Was sie DARF, muss auch gehen. Ohne diese
Zeile waere die Sperre oben auch dann gruen, wenn ihr Weg voellig
zu waere. */
/* DIREKT IN DIE DATENBANK statt ueber `anlegen()`: In diesem Block
steht weiter unten ein `const anlegen = await hole(...)`, und das
verdeckt die gleichnamige Funktion im ganzen Block -- auch OBERHALB
seiner eigenen Zeile. JavaScript nennt das die tote Zone, und sie
kostete hier einen Absturz mit der Meldung "Cannot access
'anlegen' before initialization". */
const frischerModi = (() => {
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt)"
+ " VALUES ('Probe-Modi','modi','x','y',1,1,?)").run(jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
})();
const anEinemModi = await hole(`/workspace/api/verwaltung/personen/${frischerModi}/rolle`,
{ host: CREW, methode: "PUT", keks: hand.keks, rumpf: JSON.stringify({ rolle: "gast" }) });
ok(anEinemModi.status === 200,
`einen Modi macht sie sehr wohl zur Community (${anEinemModi.status})`);
const nachher = d.prepare("SELECT rolle FROM personen WHERE id = ?").get(frischerModi)?.rolle;
ok(nachher === "gast", `und es steht so in der Datenbank (${nachher})`);
/* Eine Rolle, die sie nicht vergeben darf, bleibt zu -- sonst waere
"sie darf wechseln" ein Freibrief. */
const zuHoch = await hole(`/workspace/api/verwaltung/personen/${frischerModi}/rolle`,
{ host: CREW, methode: "PUT", keks: hand.keks, rumpf: JSON.stringify({ rolle: "hand" }) });
ok(zuHoch.status === 400,
`eine zweite rechte Hand ernennt sie nicht (${zuHoch.status})`);
const anlegen = await hole("/workspace/api/verwaltung/personen",
{ host: CREW, methode: "POST", keks: hand.keks,
rumpf: JSON.stringify({ name: "Nochwer", rolle: "modi" }) });
+32 -4
View File
@@ -896,12 +896,21 @@ personenRouter.put("/workspace/api/verwaltung/personen/:id/rolle", gleicheHerkun
Sie beantwortet drei Fragen auf einmal: nicht die eigene Rolle,
nicht DogFather, und fuer die rechte Hand auch keine zweite
rechte Hand. */
/* DIE EIGENE ZEILE ZUERST -- und mit 400, nicht 403.
Sie ist keine Rechtefrage, sondern eine unsinnige Bitte: Es
gibt niemanden, der sie duerfte. Eine 403 haette hier
ausserdem den Fall "der letzte DogFather tritt zurueck"
verschluckt, den die Sicherung weiter unten mit einem
verstaendlichen Satz beantwortet. */
if (person.id === req.person.id) {
return res.status(400).json({
fehler: "Deine eigene Rolle kannst du nicht ändern. Das muss jemand anderes tun.",
});
}
if (!darfRolleAendern(req.person, person)) {
return res.status(403).json({ fehler: "Diese Rolle darfst du nicht ändern." });
}
if (!rollenZumAendern(req.person).includes(rolle)) {
return res.status(403).json({ fehler: "Diese Rolle vergibst du nicht." });
}
/* Dieselbe Auskunft wie beim Anlegen: Wer eine Rolle nicht
vergeben darf, darf sie auch nicht zuweisen. Sonst waere das
@@ -924,7 +933,26 @@ personenRouter.put("/workspace/api/verwaltung/personen/:id/rolle", gleicheHerkun
Rolle." mit 400). Wer durchprobiert, haette daran ablesen
koennen, welche Rollen es gibt. Jetzt beide Faelle wortgleich
-- wie beim Anlegen. */
if (!darfAnlegen(req.person).includes(rolle)) {
/* WELCHE ROLLE DARF DIESE PERSON VERGEBEN.
Hier stand `darfAnlegen`. Das war richtig, solange Vergeben und
Anlegen dasselbe waren -- seit dem 20.09. sind sie es nicht
mehr: Die rechte Hand darf NIEMANDEN anlegen und trotzdem
'modi' und 'gast' vergeben, ein Manager darf einen Creator
anlegen und gar keine Rolle vergeben. Gemessen:
admin anlegen = aendern (sieben Rollen)
spicy anlegen = aendern (vier)
hand anlegen = — aendern = modi, gast
manager anlegen = creator aendern = —
WORTGLEICH MIT DEM FALL DARUEBER, und das ist Absicht: Zwei
verschiedene Antworten -- "Unbekannte Rolle" gegen "die darfst
du nicht vergeben" -- verraten beim Durchprobieren, WELCHE
Rollen es gibt. Genau davor warnt der Kommentar von 17.09., und
eine 403 an dieser Stelle hatte die Luecke am 20.09. kurz
wieder aufgemacht. */
if (!rollenZumAendern(req.person).includes(rolle)) {
return res.status(400).json({ fehler: "Unbekannte Rolle." });
}
if (person.rolle === rolle) {
+14 -1
View File
@@ -289,7 +289,20 @@ export function darfRolleAendern(person, ziel) {
if (!person || !ziel) return false;
if (person.id === ziel.id) return false; // (3)
if (!rollenZumAendern(person).length) return false;
if (person.rolle === "admin") return ziel.rolle !== "admin";
/* DOGFATHER DARF JEDEN ANFASSEN -- auch einen zweiten DogFather.
Hier stand `return ziel.rolle !== "admin"`, und das war zu scharf:
Es machte aus "DogFather ist nicht mehr VERGEBBAR" ein "an einem
DogFather ist nichts mehr zu aendern". Gemessen hat es
pruef-haus-trennung -- "solange es zwei gibt, darf einer wechseln"
bekam 403 statt 200. Ein zweiter Zugang liess sich damit nicht mehr
zurueckstufen; er waere fuer immer DogFather geblieben.
Die beiden echten Gefahren haengen woanders und bleiben: Niemand
KANN 'admin' vergeben (steht nicht in rollenZumAendern), und der
LETZTE DogFather laesst sich nicht herabstufen (Sicherung in der
Route). Die eigene Zeile ist drei Zeilen darueber schon
ausgeschlossen. */
if (person.rolle === "admin") return true;
/* Spicy Media wie bisher: das ganze Haus ausser DogFather. */
if (person.rolle === "spicy") return ziel.rolle !== "admin";
if (person.rolle === "hand") {
+7
View File
@@ -70,6 +70,13 @@
+ 'lade die Seite neu.',
ungueltig:
'Mit der Eingabe stimmt etwas nicht. Sieh die Felder noch einmal durch.',
/* Die Kachelfarbe im Chat. Der Fall entsteht nur, wenn die
Auswahl aelter ist als die Farbliste -- etwa weil die Seite seit
gestern offen liegt. Deshalb sagt der Satz, was zu tun ist, und
nicht, was schiefging. */
unbekannte_kachel:
'Diese Farbe gibt es nicht mehr. Lade die Seite neu und such dir '
+ 'eine aus der Liste aus.',
/* ---- Anmeldung und Zugang ------------------------------------ */
nicht_angemeldet: