From 96ab707bfc788f1c4e3152fb62bad082b3e2592c Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 14 Sep 2026 16:06:37 +0200 Subject: [PATCH] KI-Einstellungen: Schluessel und Modell in der Oberflaeche --- docs/01-architektur.md | 264 ++++++++++++++++++++--------------------- 1 file changed, 132 insertions(+), 132 deletions(-) diff --git a/docs/01-architektur.md b/docs/01-architektur.md index d9a71aa..3f4493f 100644 --- a/docs/01-architektur.md +++ b/docs/01-architektur.md @@ -1,132 +1,132 @@ -# 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 - -```mermaid -flowchart LR - U(("Browser")) -->|"HTTPS"| A["Apache :443
icrm.icon-experts.de"] - A -->|"/"| D["dist/
SPA, FallbackResource"] - A -->|"/api"| E["Express :5007"] - E --> C[("icon_CRM")] - - W(("Besucher")) -->|"HTTPS"| B["Apache :443
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](04-diktat.md)). - -## Anfrageweg mit Anmeldung - -```mermaid -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
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 - -```mermaid -flowchart TD - L["Anmeldung"] --> R{"Rolle"} - R -->|admin| A["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,
Laufende Projekte, Historie
Einstellungen: Nutzerverwaltung, Stammdaten, Mein Konto, Diktat"] - R -->|user| U["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,
Laufende Projekte, Historie
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`, `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 -``` +# 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 + +```mermaid +flowchart LR + U(("Browser")) -->|"HTTPS"| A["Apache :443
icrm.icon-experts.de"] + A -->|"/"| D["dist/
SPA, FallbackResource"] + A -->|"/api"| E["Express :5007"] + E --> C[("icon_CRM")] + + W(("Besucher")) -->|"HTTPS"| B["Apache :443
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](04-diktat.md)). + +## Anfrageweg mit Anmeldung + +```mermaid +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
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 + +```mermaid +flowchart TD + L["Anmeldung"] --> R{"Rolle"} + R -->|admin| A["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,
Laufende Projekte, Historie
Einstellungen: Nutzerverwaltung, Stammdaten, KI, Mein Konto, Diktat"] + R -->|user| U["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,
Laufende Projekte, Historie
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`, `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 +```