Workspace: Aufgaben bearbeiten, Personenverwaltung im Browser
Aufgaben: - Bearbeiten-Dialog (Titel, Beschreibung, Prioritaet, Frist, Zuordnung). Als natives <dialog>: Fokusfang, Esc zum Schliessen und Abdunklung ohne eigenen Code. - Loeschen nur fuer Management, mit Rueckfrage und Protokolleintrag. Das Konzept will, dass Erledigtes stehen bleibt -- Loeschen ist der Ausnahmefall fuer Fehleintraege, nicht der normale Abschluss. Personen (/workspace/personen.html, nur Management): - Anlegen, Code erneuern, sperren/entsperren, Protokollansicht - Der Code wird genau einmal in der Antwort zurueckgegeben, nie gespeichert; beim Schliessen auch aus dem Dokument entfernt - Selbstschutz: niemand kann sich selbst sperren, und das letzte aktive Management laesst sich nicht sperren -- sonst kaeme niemand mehr hinein - Code fuer sich selbst tauschen nur mit ausdruecklicher Bestaetigung, weil es die eigene Sitzung sofort beendet Zwei Fehler, die beim Testen aufgefallen sind: 1. Rollenpruefung fehlte beim Ausliefern der Seiten. Ein Creator bekam personen.html mit HTTP 200 -- die Schnittstellen wiesen ihn zwar ab, das Geruest der Seite war aber sichtbar. GESCHUETZT ist jetzt eine Zuordnung Pfad -> erlaubte Rollen statt einer blossen Liste. 2. Das Protokoll nannte den falschen Verursacher. personAnlegen trug die NEU ANGELEGTE Person als person_id ein, der Eintrag las sich also so, als haette sie sich selbst angelegt. Akteur und Betroffener sind jetzt getrennt: Akteur in person_id, Betroffener im Text. Ueber die Kommandozeile angelegte Personen zeigen korrekt keinen Akteur.
This commit is contained in:
+39
-10
@@ -247,13 +247,27 @@ export const workspaceRouter = express.Router();
|
||||
|
||||
/* Schutz der angemeldeten Seiten. Serverseitig, nicht nur im Browser --
|
||||
sonst könnte man die Seite einfach direkt aufrufen. */
|
||||
const GESCHUETZT = ["/workspace/start.html", "/workspace/aufgaben.html"];
|
||||
/* Pfad -> erlaubte Rollen. `null` heisst: jede angemeldete Rolle.
|
||||
|
||||
Die Rolle wird hier mitgeprueft und nicht nur in der Schnittstelle.
|
||||
Sonst bekommt z. B. ein Creator die Verwaltungsseite zwar ausgeliefert
|
||||
(HTTP 200) und sieht ihr Geruest, auch wenn sie danach leer bleibt und
|
||||
das Skript ihn wegschickt. Sichtbar sein soll sie gar nicht. */
|
||||
const GESCHUETZT = {
|
||||
"/workspace/start.html": null,
|
||||
"/workspace/aufgaben.html": null,
|
||||
"/workspace/personen.html": ["admin"],
|
||||
};
|
||||
|
||||
workspaceRouter.use((req, res, next) => {
|
||||
if (!GESCHUETZT.includes(req.path)) return next();
|
||||
if (!Object.hasOwn(GESCHUETZT, req.path)) return next();
|
||||
const person = sitzungLesen(req);
|
||||
if (person) return next();
|
||||
return res.redirect(302, "/workspace/");
|
||||
if (!person) return res.redirect(302, "/workspace/");
|
||||
const erlaubt = GESCHUETZT[req.path];
|
||||
if (erlaubt && !erlaubt.includes(person.rolle)) {
|
||||
return res.redirect(302, "/workspace/start.html");
|
||||
}
|
||||
return next();
|
||||
});
|
||||
|
||||
workspaceRouter.post("/workspace/api/anmelden", (req, res) => {
|
||||
@@ -343,7 +357,12 @@ export function codeErzeugen(gruppen = 4, laenge = 4) {
|
||||
return aus;
|
||||
}
|
||||
|
||||
export function personAnlegen(name, rolle) {
|
||||
/* `akteur` ist WER die Aktion ausloest -- nicht, wen sie betrifft. Das
|
||||
muss getrennt bleiben: Stand im Protokoll die neu angelegte Person als
|
||||
person_id, las sich der Eintrag so, als haette sie sich selbst angelegt.
|
||||
Wer betroffen ist, steht im Text. Ohne Akteur (Kommandozeile) bleibt
|
||||
das Feld leer. */
|
||||
export function personAnlegen(name, rolle, akteur = null) {
|
||||
if (!ROLLEN.has(rolle)) throw new Error(`Unbekannte Rolle: ${rolle}`);
|
||||
const code = codeErzeugen();
|
||||
const salt = randomBytes(16).toString("hex");
|
||||
@@ -351,11 +370,14 @@ export function personAnlegen(name, rolle) {
|
||||
const { lastInsertRowid } = db().prepare(
|
||||
"INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)"
|
||||
).run(name, rolle, hash, salt, SCRYPT.N, jetzt());
|
||||
protokolliere("person_angelegt", { personId: Number(lastInsertRowid), rolle, detail: name });
|
||||
protokolliere("person_angelegt", {
|
||||
personId: akteur?.id ?? null, rolle: akteur?.rolle ?? null,
|
||||
ip: akteur?.ip ?? null, detail: `${name} (${rolle})`,
|
||||
});
|
||||
return { id: Number(lastInsertRowid), name, rolle, code };
|
||||
}
|
||||
|
||||
export function codeNeu(id) {
|
||||
export function codeNeu(id, akteur = null) {
|
||||
const person = db().prepare("SELECT id, name, rolle FROM personen WHERE id = ?").get(id);
|
||||
if (!person) throw new Error(`Keine Person mit Nummer ${id}`);
|
||||
const code = codeErzeugen();
|
||||
@@ -365,14 +387,21 @@ export function codeNeu(id) {
|
||||
/* Alle offenen Sitzungen beenden -- ein neuer Code soll den alten Zugang
|
||||
wirklich beenden, nicht nur die nächste Anmeldung betreffen. */
|
||||
db().prepare("DELETE FROM sitzungen WHERE person_id = ?").run(id);
|
||||
protokolliere("code_erneuert", { personId: id, rolle: person.rolle, detail: person.name });
|
||||
protokolliere("code_erneuert", {
|
||||
personId: akteur?.id ?? null, rolle: akteur?.rolle ?? null,
|
||||
ip: akteur?.ip ?? null, detail: `für ${person.name}`,
|
||||
});
|
||||
return { ...person, code };
|
||||
}
|
||||
|
||||
export function personSperren(id, aktiv = 0) {
|
||||
export function personSperren(id, aktiv = 0, akteur = null) {
|
||||
const person = db().prepare("SELECT name FROM personen WHERE id = ?").get(id);
|
||||
db().prepare("UPDATE personen SET aktiv = ? WHERE id = ?").run(aktiv ? 1 : 0, id);
|
||||
if (!aktiv) db().prepare("DELETE FROM sitzungen WHERE person_id = ?").run(id);
|
||||
protokolliere(aktiv ? "person_entsperrt" : "person_gesperrt", { personId: id });
|
||||
protokolliere(aktiv ? "person_entsperrt" : "person_gesperrt", {
|
||||
personId: akteur?.id ?? null, rolle: akteur?.rolle ?? null,
|
||||
ip: akteur?.ip ?? null, detail: person?.name ?? `#${id}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function personenListe() {
|
||||
|
||||
Reference in New Issue
Block a user