5.2 KiB
01 Architektur
Bausteine
Drei Teile; keiner spricht direkt mit einem anderen außer über HTTP bzw. SQL:
| Teil | Technik | Wo |
|---|---|---|
| Frontend | React 19, TypeScript, Tailwind 4, Vite 6 | statisches Build in /var/www/icon_CRM/dist |
| Backend | Express 4, mysql2, JWT, bcrypt | /var/www/icon_CRM/server, 127.0.0.1:5007 |
| Datenbank | MariaDB | icon_CRM |
Der Browser spricht nie direkt mit MariaDB – deshalb überhaupt ein Backend.
Es bindet ausschließlich auf 127.0.0.1 und ist von außen nur über den
Apache-Reverse-Proxy erreichbar.
Das ersetzt den vorherigen Aufbau: dort lag der Supabase-Anon-Key im
Browser-Bundle und hatte vollen Lesezugriff auf alle Tabellen, icon_users
samt Passwort-Hashes eingeschlossen.
Topologie
flowchart LR
U(("Browser")) -->|"HTTPS"| A["Apache :443<br/>icrm + crm.icon-experts.de"]
A -->|"/"| D["dist/<br/>SPA, FallbackResource"]
A -->|"/api"| E["Express :5007"]
E --> C[("icon_CRM")]
W(("Besucher")) -->|"HTTPS"| B["Apache :443<br/>icon-experts.de"]
B -->|"/"| WD["icon_website/dist"]
B -->|"/api/public"| E
Beide Vhosts liegen auf demselben Rechner. Die Website reicht nur
/api/public durch – Anmeldung, Kontakte und Leads sind von dort aus
unerreichbar. Der Umweg über den eigenen Vhost ist Absicht: so bleibt der
Aufruf gleicher Herkunft und die Content-Security-Policy der Website kommt
weiter mit connect-src 'self' aus.
Ausgehende Verbindungen
Das Backend spricht von sich aus mit drei fremden Diensten. Alle sind optional – fehlen die Zugangsdaten oder ist das Ziel nicht erreichbar, läuft die Anwendung ohne den jeweiligen Teil weiter.
| Ziel | Wofür | Ausgelöst von |
|---|---|---|
api.openai.com |
CV-Auslesung, Schreibhilfe, erweiterte Suche | Benutzer, jeweils auf Klick |
pcrm.perinco.de |
Kontakte des perinco CRM lesen | Schalter „perinco" in den Kontakten |
linkedin.com, media.licdn.com |
Profilfoto zu einem Kontakt holen | Knopf im Kontaktformular |
Das Diktat taucht in dieser Liste nicht auf: es lädt sein Modell direkt aus dem Browser vom Hugging-Face-CDN, das Backend ist daran nicht beteiligt (siehe 04 Diktat).
Anfrageweg mit Anmeldung
sequenceDiagram
actor B as Browser
participant A as Apache
participant E as Express
participant M as MariaDB
B->>A: POST /api/auth/login
A->>E: weiterleiten
E->>M: SELECT icon_users WHERE username OR email
M-->>E: Zeile mit Hash
E->>E: bcrypt.compare (SHA-256 des Altbestands wird dabei umgeschrieben)
E-->>B: { token (JWT, 12 h), user }
Note over B: Token im localStorage
B->>A: GET /api/contacts<br/>Authorization: Bearer ...
A->>E: weiterleiten
E->>E: requireAuth: JWT pruefen
E->>M: SELECT icon_users WHERE id (noch aktiv?)
E->>M: SELECT contacts JOIN companies
M-->>E: Zeilen
E-->>B: JSON
Bei 401 löscht das Frontend das Token und springt auf den Anmeldeschirm
(setUnauthorizedHandler in src/lib/api.ts). Das Token wird bei jeder Anfrage
neu gegen die Datenbank geprüft – ein deaktivierter Benutzer ist sofort draußen,
ohne auf den Ablauf des Tokens zu warten.
Passwörter aus dem Altbestand: im alten CRM lagen sie als ungesalzenes SHA-256. Diese Hashes sind an ihrer Form erkennbar (64 Hexziffern) und werden beim ersten erfolgreichen Login auf bcrypt umgeschrieben. Neu vergeben wird ausschließlich bcrypt.
Rollen
flowchart TD
L["Anmeldung"] --> R{"Rolle"}
R -->|admin| A["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,<br/>Laufende Projekte, Historie<br/>Einstellungen: Nutzerverwaltung, Stammdaten, KI, Mein Konto, Diktat"]
R -->|user| U["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,<br/>Laufende Projekte, Historie<br/>Einstellungen: Mein Konto, Diktat"]
Die Prüfung sitzt doppelt: das Menü blendet aus, was nicht erlaubt ist, und
das Backend weist zusätzlich mit 403 ab (requireAdmin). Ausnahme ist
GET /api/users/directory – die Benutzerliste brauchen alle Rollen für
Erwähnungen, Zuständigkeiten und die Farben in der Historie. Passwort-Hashes
liefert der Endpunkt nie.
Ein Sicherheitsnetz im Backend verhindert, dass der letzte aktive Administrator degradiert, deaktiviert oder gelöscht wird.
Der Reiter Diktat ist bewusst für jede Rolle sichtbar: jeder richtet die Spracherkennung auf seinem eigenen Gerät ein, mit seinem eigenen Download.
Belegte Ports
| Port | Dienst |
|---|---|
| 5007 | ICON CRM Backend (nur 127.0.0.1) |
| 3101 | Vite-Dev-Server (nur lokal beim Entwickeln) |
| 80/443 | Apache: icrm.icon-experts.de (+ Alias crm.icon-experts.de), icon-experts.de, www.icon-experts.de |
Was wo liegt
/var/www/icon_CRM/dist SPA (Build)
/var/www/icon_CRM/server Backend samt .env (chmod 600)
/var/www/icon_CRM/uploads hochgeladene Lebenslaeufe
/var/www/icon_website/dist ICON Website
/etc/systemd/system/icon-crm-backend.service
/etc/apache2/sites-available/icrm.icon-experts.de*.conf
/etc/apache2/sites-available/icon-experts.de*.conf