Die Community-Rolle laesst sich endlich anlegen
Filipe: "und wieso kann ich immer noch keine community rolle
erstellen?"
Nachgemessen statt geraten: Die Auskunft schickte "gast/Community" als
waehlbare Rolle mit, die Oberflaeche baute den Knopf daraus -- und das
Anlegen antwortete 400 "Unbekannte Rolle." Genau so reproduziert.
DIE URSACHE: EINE LISTE, ZWEI FRAGEN
Die Pruefung las `ROLLEN`, und `ROLLEN` ist `ROLLEN_REIHE` -- die
SORTIERREIHENFOLGE. Dort fehlt "gast" mit Absicht; der Kommentar in
workspace.js sagt es woertlich ("steht mit Absicht NICHT in der Liste,
sie faellt ans Ende").
`ROLLEN_REIHE` beantwortet "in welcher Reihenfolge", nicht "welche gibt
es". Genau davor wird in diesem Haus an zehn Stellen gewarnt -- und
hier stand es vier Zeilen ueber der Stelle, an der es passiert ist.
Gefragt wird jetzt `darfAnlegen` -- dieselbe Auskunft, aus der die
Oberflaeche ihre Knoepfe baut. Damit koennen die beiden nicht mehr
auseinanderlaufen.
ZWEI NEBENBEFUNDE, beide groesser als der gemeldete Fehler:
1. DIE ALTE PRUEFUNG LIESS "admin" DURCH. `ROLLEN_REIHE` enthaelt sie.
Filipes Regel vom 11.09. ("dogfather soll man nicht auswaehlen
koennen, das ist die einzige die man nicht auswaehlen kann") stand
nur in ANLEGBAR -- und die wurde hier nicht gefragt. Es liess sich
also ein zweiter DogFather-Zugang anlegen.
2. DIESELBE ZEILE STAND EIN ZWEITES MAL, beim Rollenwechsel. Auch eine
bestehende Person liess sich nicht zu "Community" machen. Dort stand
die richtige Pruefung (`darfAnlegen`) schon direkt darunter -- die
falsche davor kam ihr nur zuvor. Und sie antwortete gespraechiger
(403 "Diese Rolle vergibst du nicht." gegen 400 "Unbekannte
Rolle."); wer durchprobiert, haette daran ablesen koennen, welche
Rollen es gibt. Jetzt beide Faelle wortgleich.
Gefunden hat das nicht das Lesen, sondern die neue Pruefung: Ihre
Suche nach der alten Zeile reichte in die Nachbarroute hinein.
NEU: pruef-rollen-anlegen (11 Pruefungen)
Sie schreibt keine Liste ab, sondern holt sich die Rollen aus
`darfAnlegen` -- derselben Quelle wie Route und Oberflaeche -- und legt
JEDE davon wirklich an. Eine eigene Liste waere eine dritte gewesen und
damit dieselbe Falle noch einmal.
Dazu die Gegenproben: eine erfundene Rolle, eine leere, und ein zweiter
DogFather -- alle drei abgelehnt.
pruef-rollen 315, pruef-personen-formular 36, pruef-personen-liste,
pruef-rechtetafel 19: gruen.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
/* =====================================================================
|
||||
WAS DIE OBERFLÄCHE ANBIETET, MUSS DER SERVER AUCH ANNEHMEN
|
||||
(17.09.2026)
|
||||
|
||||
Filipe: "und wieso kann ich immer noch keine community rolle
|
||||
erstellen?"
|
||||
|
||||
Nachgemessen: Die Auskunft `/api/verwaltung/personen` schickte
|
||||
"gast/Community" als waehlbare Rolle mit, die Oberflaeche baute den
|
||||
Knopf daraus -- und das Anlegen antwortete "Unbekannte Rolle."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DIE URSACHE: EINE LISTE, ZWEI FRAGEN
|
||||
|
||||
Die Pruefung las `ROLLEN`, und `ROLLEN` ist `ROLLEN_REIHE` -- die
|
||||
SORTIERREIHENFOLGE. Dort fehlt "gast" mit Absicht; der Kommentar in
|
||||
workspace.js sagt es woertlich ("steht mit Absicht NICHT in der
|
||||
Liste, sie faellt ans Ende").
|
||||
|
||||
`ROLLEN_REIHE` beantwortet "in welcher Reihenfolge", nicht "welche
|
||||
gibt es". Genau davor wird in diesem Haus an zehn Stellen gewarnt.
|
||||
|
||||
NEBENBEFUND, und er war die groessere Luecke: Die alte Pruefung
|
||||
liess "admin" durch -- `ROLLEN_REIHE` enthaelt sie. Filipes Regel vom
|
||||
11.09. ("dogfather soll man nicht auswaehlen koennen, das ist die
|
||||
einzige die man nicht auswaehlen kann") stand nur in `ANLEGBAR`, und
|
||||
die wurde hier nicht gefragt.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DIE PRUEFUNG LEITET AB STATT ABZUSCHREIBEN
|
||||
|
||||
Sie holt sich die angebotenen Rollen aus derselben Auskunft, aus der
|
||||
die Oberflaeche ihre Knoepfe baut, und legt JEDE davon wirklich an.
|
||||
Eine eigene Liste hier waere eine DRITTE -- und damit dieselbe Falle
|
||||
noch einmal.
|
||||
|
||||
Aufrufen mit: node server/pruef-rollen-anlegen.mjs
|
||||
===================================================================== */
|
||||
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { portMussFreiSein } from "./helfer-port.mjs";
|
||||
|
||||
const PORT = await portMussFreiSein(4497, "pruef-rollen-anlegen");
|
||||
const ordner = mkdtempSync(join(tmpdir(), "ws-ra-"));
|
||||
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(180_000, "pruef-rollen-anlegen");
|
||||
await new Promise((r) => setTimeout(r, 700));
|
||||
const BASIS = `http://127.0.0.1:${PORT}`;
|
||||
|
||||
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
||||
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
||||
|
||||
let fehler = 0, geprueft = 0;
|
||||
const melde = (t) => console.log(t);
|
||||
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
||||
|
||||
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 salt = randomBytes(16).toString("hex");
|
||||
const hash = scryptSync("CODE-DOGI-0001", 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("Filipe", "admin", hash, salt, 32768,
|
||||
createHmac("sha256", sch).update("CODE-DOGI-0001").digest("hex"), jetzt);
|
||||
d.close();
|
||||
|
||||
const an = await fetch(BASIS + "/workspace/api/anmelden", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }),
|
||||
});
|
||||
const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
|
||||
const kopf = { "Content-Type": "application/json", cookie: keks, origin: BASIS };
|
||||
|
||||
async function anlegen(name, rolle) {
|
||||
const a = await fetch(BASIS + "/workspace/api/verwaltung/personen", {
|
||||
method: "POST", headers: kopf, body: JSON.stringify({ name, rolle }),
|
||||
});
|
||||
return { status: a.status, daten: await a.json().catch(() => null) };
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
1. WAS DIE OBERFLAECHE ANBIETET
|
||||
======================================================================= */
|
||||
melde("");
|
||||
melde("=== 1. Das Angebot ===");
|
||||
const v = await fetch(BASIS + "/workspace/api/verwaltung/personen", { headers: { cookie: keks } });
|
||||
const auskunft = await v.json().catch(() => null);
|
||||
const angeboten = [
|
||||
...(auskunft?.rollen || []).map((r) => (typeof r === "string" ? r : r.wert)),
|
||||
...(auskunft?.zusatzrollen || []).map((r) => r.wert),
|
||||
].filter(Boolean);
|
||||
{
|
||||
/* DREI, NICHT MEHR -- und das ist richtig so: Die Grundrollen
|
||||
(Creator, Scout, Manager, Spicy) stehen als Knoepfe in der
|
||||
HTML-Seite, nur die ZUSATZROLLEN kommen aus der Auskunft. Beim
|
||||
ersten Lauf habe ich fuenf erwartet und die Pruefung rot gemacht,
|
||||
nicht den Code. */
|
||||
ok(v.status === 200 && angeboten.length >= 3,
|
||||
`die Auskunft bietet ${angeboten.length} Zusatzrollen an (${angeboten.join(", ")})`);
|
||||
ok(angeboten.includes("gast"), "darunter die Community-Rolle");
|
||||
|
||||
/* UND DIE VOLLE MENGE kommt aus derselben Quelle wie die Route:
|
||||
`darfAnlegen`. Damit deckt Abschnitt 2 wirklich alles ab, was
|
||||
jemand anlegen koennen soll -- nicht nur die Zusatzrollen. */
|
||||
const { darfAnlegen } = await import("./workspace.js");
|
||||
const alleErlaubt = darfAnlegen({ rolle: "admin", haus: "workspace" });
|
||||
ok(alleErlaubt.length >= 6,
|
||||
`darfAnlegen nennt ${alleErlaubt.length} Rollen (${alleErlaubt.join(", ")})`);
|
||||
for (const r of alleErlaubt) if (!angeboten.includes(r)) angeboten.push(r);
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
2. UND DER SERVER NIMMT JEDE DAVON AN
|
||||
======================================================================= */
|
||||
melde("");
|
||||
melde("=== 2. Die Annahme ===");
|
||||
{
|
||||
/* DAS IST DER EIGENTLICHE PUNKT. Ein Knopf, den der Server ablehnt,
|
||||
ist schlimmer als kein Knopf: Man tippt einen Namen, drueckt, und
|
||||
bekommt eine Absage fuer etwas, das angeboten wurde. */
|
||||
const abgelehnt = [];
|
||||
let n = 0;
|
||||
for (const rolle of angeboten) {
|
||||
const a = await anlegen(`Probe ${++n}`, rolle);
|
||||
if (a.status !== 201) abgelehnt.push(`${rolle} (${a.status} ${a.daten?.fehler || ""})`);
|
||||
}
|
||||
ok(abgelehnt.length === 0,
|
||||
abgelehnt.length ? `abgelehnt: ${abgelehnt.join(" · ")}`
|
||||
: `alle ${angeboten.length} angebotenen Rollen lassen sich anlegen`);
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
3. UND WAS NICHT ANGEBOTEN WIRD, GEHT AUCH NICHT
|
||||
======================================================================= */
|
||||
melde("");
|
||||
melde("=== 3. Die Gegenprobe ===");
|
||||
{
|
||||
/* OHNE DIESEN ABSCHNITT hiesse "alles geht" nur, dass gar nicht
|
||||
geprueft wird -- man koennte die Pruefung bestehen, indem man sie
|
||||
ganz entfernt. */
|
||||
const x = await anlegen("Erfunden", "oberchef");
|
||||
ok(x.status === 400, `eine erfundene Rolle wird abgelehnt (${x.status})`);
|
||||
|
||||
/* UND DER NEBENBEFUND VON HEUTE: "admin" stand in ROLLEN_REIHE und
|
||||
rutschte damit durch die alte Pruefung. Filipes Regel vom 11.09.
|
||||
("dogfather soll man nicht auswaehlen koennen") stand nur in
|
||||
ANLEGBAR -- und die wurde nicht gefragt. */
|
||||
const a = await anlegen("Zweiter DogFather", "admin");
|
||||
ok(a.status === 400,
|
||||
`ein zweiter DogFather-Zugang laesst sich NICHT anlegen (${a.status})`);
|
||||
ok(!angeboten.includes("admin"), "und die Oberflaeche bietet ihn auch nicht an");
|
||||
|
||||
/* Ein leerer Wert ist keine Rolle. */
|
||||
const l = await anlegen("Ohne Rolle", "");
|
||||
ok(l.status === 400, `eine leere Rolle ebenfalls nicht (${l.status})`);
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
4. DIE BEIDEN LISTEN STEHEN NICHT MEHR NEBENEINANDER
|
||||
======================================================================= */
|
||||
melde("");
|
||||
melde("=== 4. Eine Quelle, nicht zwei ===");
|
||||
{
|
||||
const { readFileSync } = await import("node:fs");
|
||||
const quelle = readFileSync(new URL("./workspace-personen.js", import.meta.url), "utf8");
|
||||
/* KOMMENTARE RAUS, bevor gesucht wird. Beim ersten Lauf ist die
|
||||
Pruefung an ihrem eigenen Erfolg gescheitert: Der Kommentar, der
|
||||
den behobenen Fehler BESCHREIBT, enthaelt den alten Ausdruck --
|
||||
und die Suche fand ihn. Eine Pruefung, die Beschreibungen fuer
|
||||
Code haelt, misst die Dokumentation. */
|
||||
const ohneKommentar = quelle
|
||||
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
||||
.replace(/\/\/[^\r\n]*/g, " ");
|
||||
const route = ohneKommentar.slice(
|
||||
ohneKommentar.indexOf('personenRouter.post("/workspace/api/verwaltung/personen"'));
|
||||
const stelle = route.slice(0, 3000);
|
||||
ok(/darfAnlegen\(req\.person\)\.includes\(rolle\)/.test(stelle),
|
||||
"die Route fragt darfAnlegen");
|
||||
/* UND NICHT MEHR DIE SORTIERREIHENFOLGE. Ein Kommentar daneben
|
||||
wuerde es nicht verhindern -- das steht so in den Hausregeln. */
|
||||
ok(!/!ROLLEN\.includes\(rolle\)/.test(stelle),
|
||||
"und nicht mehr die Sortierreihenfolge ROLLEN_REIHE");
|
||||
|
||||
/* ROLLEN_REIHE enthaelt "gast" weiterhin NICHT -- das ist richtig
|
||||
so, sie ist eine Sortierung. Steht sie eines Tages doch drin, war
|
||||
jemand an der falschen Stelle. */
|
||||
const { ROLLEN_REIHE } = await import("./workspace.js");
|
||||
ok(!ROLLEN_REIHE.includes("gast"),
|
||||
`ROLLEN_REIHE bleibt eine Sortierung ohne "gast" (${ROLLEN_REIHE.join(", ")})`);
|
||||
}
|
||||
|
||||
melde("");
|
||||
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
||||
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
||||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
||||
process.exit(fehler ? 1 : 0);
|
||||
@@ -730,9 +730,32 @@ personenRouter.post("/workspace/api/verwaltung/personen", gleicheHerkunft, (req,
|
||||
10.09.2026 sind es zwei, und die zweite waere hier sonst still
|
||||
durchgerutscht -- ein Manager haette eine rechte Hand anlegen
|
||||
koennen, ohne dass irgendwo etwas rot wird. */
|
||||
const unbekannt = !ROLLEN.includes(rolle)
|
||||
|| (TEAM_DOGI_ROLLEN.has(rolle) && !istDogFather(req.person));
|
||||
if (unbekannt) return res.status(400).json({ fehler: "Unbekannte Rolle." });
|
||||
/* GEFRAGT WIRD `darfAnlegen`, NICHT `ROLLEN` (17.09.2026).
|
||||
|
||||
HIER STAND `!ROLLEN.includes(rolle)`, und `ROLLEN` ist
|
||||
`ROLLEN_REIHE` -- die SORTIERREIHENFOLGE. Dort fehlt "gast" mit
|
||||
Absicht (der Kommentar in workspace.js sagt es woertlich: "steht
|
||||
mit Absicht NICHT in der Liste, sie faellt ans Ende").
|
||||
|
||||
Folge: Die Oberflaeche bot "Community" an -- sie holt ihre
|
||||
Knoepfe aus `darfAnlegen` --, und der Server antwortete beim
|
||||
Anlegen "Unbekannte Rolle." Filipe, am 17.09.: "und wieso kann
|
||||
ich immer noch keine community rolle erstellen?"
|
||||
|
||||
Das ist die Falle, vor der in diesem Haus an zehn Stellen
|
||||
gewarnt wird: EINE Liste, zwei Fragen. `ROLLEN_REIHE` beantwortet
|
||||
"in welcher Reihenfolge", nicht "welche gibt es".
|
||||
|
||||
`darfAnlegen` beantwortet genau die Frage, die hier zaehlt --
|
||||
und es ist dieselbe Auskunft, aus der die Oberflaeche ihre
|
||||
Knoepfe baut. Damit koennen die beiden nicht mehr auseinander-
|
||||
laufen. Die Team-Dogi-Regel steckt schon darin (ANLEGBAR gibt
|
||||
"hand" und "modi" nur DogFather), ebenso die Regel, dass sich
|
||||
kein zweiter DogFather anlegen laesst -- die das alte `ROLLEN`
|
||||
uebrigens NICHT abgedeckt hat. */
|
||||
if (!darfAnlegen(req.person).includes(rolle)) {
|
||||
return res.status(400).json({ fehler: "Unbekannte Rolle." });
|
||||
}
|
||||
|
||||
/* ERSTER VORBEHALT: Eine Leitung anlegen darf nur DogFather.
|
||||
Duerfte ein Manager das, koennte er sich einen zweiten Zugang mit
|
||||
@@ -859,9 +882,26 @@ personenRouter.put("/workspace/api/verwaltung/personen/:id/rolle", gleicheHerkun
|
||||
/* Dieselbe Auskunft wie beim Anlegen: Wer eine Rolle nicht
|
||||
vergeben darf, darf sie auch nicht zuweisen. Sonst waere das
|
||||
Zuweisen der bequemere Weg an der Regel vorbei. */
|
||||
if (!ROLLEN.includes(rolle)) return res.status(400).json({ fehler: "Unbekannte Rolle." });
|
||||
/* EINE FRAGE, EINE ANTWORT (17.09.2026).
|
||||
|
||||
Hier standen ZWEI Pruefungen untereinander. Die erste las
|
||||
`ROLLEN` -- die Sortierreihenfolge, in der "gast" mit Absicht
|
||||
fehlt -- und lehnte deshalb ab, BEVOR die zweite, richtige
|
||||
ueberhaupt drankam. Eine bestehende Person liess sich damit
|
||||
nicht zu "Community" machen, aus demselben Grund wie beim
|
||||
Anlegen.
|
||||
|
||||
Gefunden hat das nicht das Lesen, sondern pruef-rollen-anlegen:
|
||||
Die Suche nach der alten Zeile reichte noch in diese Route
|
||||
hinein, und dort stand sie ein zweites Mal.
|
||||
|
||||
DIE ZWEITE ANTWORT WAR AUSSERDEM GESPRAECHIGER als die erste
|
||||
("Diese Rolle vergibst du nicht." mit 403 gegen "Unbekannte
|
||||
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)) {
|
||||
return res.status(403).json({ fehler: "Diese Rolle vergibst du nicht." });
|
||||
return res.status(400).json({ fehler: "Unbekannte Rolle." });
|
||||
}
|
||||
if (person.rolle === rolle) {
|
||||
return res.status(400).json({ fehler: "Diese Rolle hat sie schon." });
|
||||
|
||||
Reference in New Issue
Block a user