Kundenportal: Server-Seite mit strikter Mandantentrennung
Masterplan S.12. Anmeldung, Uebersicht, Projektdetail, Aenderungswuensche, Zusatzangebote annehmen/ablehnen, Nachrichten, Profil. DIE ZENTRALE ENTSCHEIDUNG: Die Trennung zwischen Kunden sitzt NICHT in der Oberflaeche, sondern in jeder einzelnen Abfrage. Ueberall steht "WHERE id = ? AND kunde_id = ?" statt "WHERE id = ?", wobei die kunde_id IMMER aus der Sitzung kommt, nie aus der Anfrage. Wer eine fremde Projektkennung errät, bekommt dadurch "nicht gefunden" statt Daten. Die Oberflaeche liegt im Browser des Kunden und ist beliebig manipulierbar -- sie kann diese Aufgabe grundsaetzlich nicht uebernehmen. Weitere bewusste Entscheidungen: * KEINE Selbstregistrierung. Masterplan S.12 verlangt, dass Projektkauf oder Betreuung VOR der Freischaltung geprueft werden. Ein Portal, in das sich jeder selbst eintraegt, waere das Gegenteil. Kunden werden in der Verwaltung angelegt und bekommen einen Einladungslink. * Passwoerter mit scrypt (in Node eingebaut, kein Zusatzpaket). Ein SHA-256 ueber ein Passwort ist milliardenfach pro Sekunde durchprobierbar; scrypt ist absichtlich langsam UND speicherhungrig. * Mindestanforderung ist LAENGE, nicht Zeichenklassen. "Hund1234!" erfuellt jede Klassenregel und ist trotzdem schlecht; "mein blauer stuhl steht krumm" erfuellt keine und ist ausgezeichnet. * Gleiche Fehlermeldung bei unbekannter Adresse und falschem Passwort -- sonst lassen sich Kundenadressen durchprobieren. * Die Anmeldesperre haengt an der E-Mail, nicht an der IP. Eine IP-Sperre wuerde mehrere Kunden hinter demselben Firmenanschluss gemeinsam aussperren -- genau der Fehler, der im Universe am 19.08.2026 auftrat. * Sperre und Passwortwechsel beenden laufende Sitzungen SOFORT, nicht erst nach zwoelf Stunden. * Ein Zusatzangebot laesst sich nur annehmen, wenn es beziffert ist -- sonst entstuende eine Zahlungspflicht ohne Preis. Dazu der wichtigste Test des Bereichs (test-webdesign-portal.mjs): zwei echte Kunden, und Kunde B versucht systematisch mit Kunde As echten Kennungen an dessen Projekt, Nachrichten, Dateien und Zusatzangebote zu kommen. Prueft ausserdem, dass interne Notizen und interne Arbeitsdateien auch dem BERECHTIGTEN Kunden verborgen bleiben. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -86,6 +86,15 @@ import {
|
||||
anfrageAbsenden, anfragenListe, anfrageDetail,
|
||||
anfrageStatus, anfrageArchivieren, anfrageNotiz,
|
||||
} from "./routes/webdesign-anfragen.js";
|
||||
/* Kundenportal (Masterplan S.12). Eigene Anmeldung mit eigener Sitzung --
|
||||
Kunden sind KEINE Teammitglieder und duerfen mit deren Rechtesystem
|
||||
nichts zu tun haben. Die Mandantentrennung sitzt in den Handlern:
|
||||
jede Abfrage filtert auf die kunde_id aus der Sitzung. */
|
||||
import {
|
||||
portalAnmelden, portalAbmelden, portalEinladungPruefen, portalPasswortSetzen,
|
||||
portalUebersicht, portalProjekt, portalAenderungEinreichen,
|
||||
portalAenderungEntscheiden, portalNachricht, portalProfilSpeichern,
|
||||
} from "./routes/webdesign-portal.js";
|
||||
|
||||
initDb();
|
||||
|
||||
@@ -227,6 +236,20 @@ app.post("/webdesign/admin/anfragen/:id/status", wrap(anfrageStatus));
|
||||
app.post("/webdesign/admin/anfragen/:id/archivieren", wrap(anfrageArchivieren));
|
||||
app.post("/webdesign/admin/anfragen/:id/notiz", wrap(anfrageNotiz));
|
||||
|
||||
/* ---------- Kundenportal ----------
|
||||
Oeffentlich sind nur Anmeldung und das Einloesen eines Einladungslinks.
|
||||
Alles Weitere prueft die Kundensitzung im Handler selbst. */
|
||||
app.post("/webdesign/portal/anmelden", wrap(portalAnmelden));
|
||||
app.post("/webdesign/portal/abmelden", wrap(portalAbmelden));
|
||||
app.get("/webdesign/portal/einladung", wrap(portalEinladungPruefen));
|
||||
app.post("/webdesign/portal/passwort", wrap(portalPasswortSetzen));
|
||||
app.get("/webdesign/portal/uebersicht", wrap(portalUebersicht));
|
||||
app.get("/webdesign/portal/projekt/:id", wrap(portalProjekt));
|
||||
app.post("/webdesign/portal/aenderung", wrap(portalAenderungEinreichen));
|
||||
app.post("/webdesign/portal/aenderung/:id/entscheiden", wrap(portalAenderungEntscheiden));
|
||||
app.post("/webdesign/portal/nachricht", wrap(portalNachricht));
|
||||
app.post("/webdesign/portal/profil", wrap(portalProfilSpeichern));
|
||||
|
||||
app.post("/supporter/register", wrap(registerSupporter));
|
||||
app.post("/supporter/verify-email", wrap(verifySupporterEmail));
|
||||
// Normaler Login seit 21.08.2026: E-Mail + selbst gewählter Zugangscode.
|
||||
|
||||
Reference in New Issue
Block a user