Creator Workspace: Anmeldung, Sitzungen und Audit-Log
Erster funktionierender Login fuer /workspace. Bewusst OHNE neue Abhaengigkeiten: Node 24 bringt node:sqlite mit, Hashing und Zufall kommen aus node:crypto. Nichts zu kompilieren, keine fremde Lieferkette an einer Stelle, an der es um Zugangsdaten geht. Sicherheit: - Codes liegen nur als scrypt-Hash (N=32768) mit eigenem Salt in der DB - Vergleich in konstanter Zeit (timingSafeEqual) - Sitzungstoken 32 Byte Zufall, in der DB nur als SHA-256 - Cookie httpOnly, SameSite=lax, Path=/workspace, secure abhaengig von req.secure (live immer an, nur der lokale http-Test kommt ohne aus) - Sperre nach 8 Fehlversuchen je IP fuer 10 Minuten -- danach ist auch der richtige Code blockiert (geprueft) - gleiche Fehlermeldung bei falscher Rolle und falschem Code - Audit-Log fuer Anmeldung, Fehlversuch, Sperre, Abmeldung, Codewechsel Die Datenbank liegt AUSSERHALB des Repos (../workspace-daten/): sonst waere sie ueber express.static aus dem Netz erreichbar, und ein git pull wuerde Nutzdaten anfassen. workspace.js kann die Website nicht mitreissen: node:sqlite wird erst bei Bedarf per createRequire geladen, jede Route faengt ihre Fehler selbst ab. Faellt die DB aus, antwortet nur /workspace/api/* mit 503. Codes werden ausschliesslich auf der Kommandozeile erzeugt (server/workspace-code.js) und dort einmal angezeigt -- nie in Git. Ausserdem: start.html als geschuetzte Seite nach dem Anmelden. Der Schutz sitzt serverseitig vor express.static, nicht nur im Browser.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env node
|
||||
/* =====================================================================
|
||||
workspace-code.js — Zugangscodes für den Creator Workspace verwalten.
|
||||
|
||||
Läuft NUR auf der Kommandozeile des Servers. Der Code wird dabei einmal
|
||||
angezeigt und danach nirgends mehr gespeichert -- in der Datenbank liegt
|
||||
ausschließlich sein scrypt-Hash. Geht ein Code verloren, wird ein neuer
|
||||
erzeugt; wiederherstellen lässt er sich nicht. Das ist Absicht.
|
||||
|
||||
Aufrufe:
|
||||
node server/workspace-code.js liste
|
||||
node server/workspace-code.js anlegen "Dogfather" admin
|
||||
node server/workspace-code.js neu 3
|
||||
node server/workspace-code.js sperren 3
|
||||
node server/workspace-code.js entsperren 3
|
||||
node server/workspace-code.js protokoll [anzahl]
|
||||
===================================================================== */
|
||||
|
||||
import {
|
||||
personAnlegen, personenListe, personSperren, codeNeu, protokollLesen,
|
||||
} from "./workspace.js";
|
||||
|
||||
const [, , befehl, ...rest] = process.argv;
|
||||
|
||||
function zeigeCode(p) {
|
||||
const linie = "─".repeat(52);
|
||||
console.log(`\n${linie}`);
|
||||
console.log(` ${p.name} · Rolle: ${p.rolle} · Nummer: ${p.id}`);
|
||||
console.log(` Zugangscode: ${p.code}`);
|
||||
console.log(`${linie}`);
|
||||
console.log(" Dieser Code wird NUR JETZT angezeigt. Sicher weitergeben");
|
||||
console.log(" (z. B. über Bitwarden) und hier nicht notieren.\n");
|
||||
}
|
||||
|
||||
try {
|
||||
switch (befehl) {
|
||||
case "anlegen": {
|
||||
const [name, rolle] = rest;
|
||||
if (!name || !rolle) throw new Error('Aufruf: anlegen "Name" <admin|creator|scout>');
|
||||
zeigeCode(personAnlegen(name, rolle));
|
||||
break;
|
||||
}
|
||||
|
||||
case "neu": {
|
||||
const id = Number(rest[0]);
|
||||
if (!id) throw new Error("Aufruf: neu <Nummer>");
|
||||
zeigeCode(codeNeu(id));
|
||||
console.log(" Alle offenen Sitzungen dieser Person wurden beendet.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case "sperren":
|
||||
case "entsperren": {
|
||||
const id = Number(rest[0]);
|
||||
if (!id) throw new Error(`Aufruf: ${befehl} <Nummer>`);
|
||||
personSperren(id, befehl === "entsperren");
|
||||
console.log(`Person ${id} ist jetzt ${befehl === "entsperren" ? "aktiv" : "gesperrt"}.`);
|
||||
break;
|
||||
}
|
||||
|
||||
case "liste": {
|
||||
const alle = personenListe();
|
||||
if (!alle.length) { console.log("Noch keine Personen angelegt."); break; }
|
||||
console.log("\n Nr Rolle Name Status Sitzungen Letzte Anmeldung");
|
||||
console.log(" ".padEnd(78, "─"));
|
||||
for (const p of alle) {
|
||||
console.log(
|
||||
` ${String(p.id).padStart(2)} ${p.rolle.padEnd(8)} ${p.name.slice(0, 20).padEnd(20)} `
|
||||
+ `${(p.aktiv ? "aktiv" : "gesperrt").padEnd(9)} ${String(p.sitzungen).padStart(6)} `
|
||||
+ `${p.letzter_login ? p.letzter_login.slice(0, 16).replace("T", " ") : "—"}`);
|
||||
}
|
||||
console.log("");
|
||||
break;
|
||||
}
|
||||
|
||||
case "protokoll": {
|
||||
const eintraege = protokollLesen(Number(rest[0]) || 20);
|
||||
if (!eintraege.length) { console.log("Protokoll ist leer."); break; }
|
||||
console.log("");
|
||||
for (const e of eintraege) {
|
||||
console.log(` ${e.zeitpunkt.slice(0, 19).replace("T", " ")} ${(e.aktion || "").padEnd(24)} `
|
||||
+ `${(e.rolle || "—").padEnd(8)} ${(e.detail || "").padEnd(18)} ${e.ip || ""}`);
|
||||
}
|
||||
console.log("");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.log(`
|
||||
Zugangscodes für den Creator Workspace
|
||||
|
||||
liste alle Personen anzeigen
|
||||
anlegen "Name" <rolle> Person anlegen, Code wird einmal angezeigt
|
||||
neu <Nr> neuen Code erzeugen (beendet alte Sitzungen)
|
||||
sperren <Nr> / entsperren <Nr> Zugang sperren oder wieder freigeben
|
||||
protokoll [Anzahl] letzte Einträge des Audit-Logs
|
||||
|
||||
Rollen: admin, creator, scout
|
||||
`);
|
||||
}
|
||||
} catch (fehler) {
|
||||
console.error(`\nFehler: ${fehler.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user