- Kopfleiste meldet immer über das Portal ab, auch ohne Portal-Sitzung, und gibt die aktuelle Seite als returnTo mit. - Portal merkt sich den Rücksprung über den Umweg zu Authentik. - Setup-Skript setzt den Abmelde-Ablauf des Providers 'Portal' auf default-invalidation-flow (der Provider-Ablauf meldet nur bei der App ab). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
141 lines
8.0 KiB
Markdown
141 lines
8.0 KiB
Markdown
# Handy-Insel Portal
|
|
|
|
Zentrale Startseite für alle internen Tools der Handy-Insel, nach dem Vorbild von openDesk:
|
|
|
|
- **Portal** (`https://portal.handy-insel.de`): Begrüßung, Kacheln aller Tools, für die man freigeschaltet ist, und „Aktuelles“ für interne Ankündigungen.
|
|
- **Kopfleiste `<hi-topbar>`**: App-Umschalter (9-Punkte-Menü), Benutzermenü und Abmelden. Jedes Tool bindet sie mit einer Zeile ein, dadurch fühlt sich alles wie ein System an.
|
|
- **Authentik** (`https://login.handy-insel.de`) bleibt die einzige Quelle für Benutzer, Gruppen und Rechte. Das Portal fragt Authentik, welche Apps ein Benutzer sehen darf. Wer in Authentik keinen Zugriff auf eine App hat, sieht auch keine Kachel.
|
|
|
|
```
|
|
┌──────────── Authentik (SSO, Gruppen, App-Rechte) ───────────┐
|
|
Browser ─► Pangolin ─┬─► portal.handy-insel.de (Kacheln, Aktuelles, /api/navigation, /topbar.js)
|
|
├─► preis. / vvl. / zeit. / timer. / time. / data. / service.
|
|
│ └─ laden https://portal.handy-insel.de/topbar.js
|
|
└─► …
|
|
```
|
|
|
|
## Rechte (Authentik-Gruppen)
|
|
|
|
| Gruppe | Mitglieder | sieht zusätzlich |
|
|
|---|---|---|
|
|
| `HI-Mitarbeiter` | alle Mitarbeiter | Preisliste, VVL, Zeiterfassung, Timer, Kundenkarte, Datenübertragungen, Service, Portal |
|
|
| `HI-Leitung` | Hubert | Paperless; darf „Aktuelles“ schreiben |
|
|
| `HI-Admin` | Kerim, Michi | Paperless, PatchMon, Dev-Instanzen; darf „Aktuelles“ schreiben |
|
|
|
|
Die Zuordnung *App → Gruppen* steht in `scripts/authentik-setup.py` (`APP_ACCESS`) und wird damit in Authentik gesetzt.
|
|
Das Feld **Gruppe** einer Authentik-Application bestimmt den Bereich im Portal (leer = „Anwendungen“, z.B. „Administration“).
|
|
Eigene Icons können in Authentik hochgeladen werden, sonst nimmt das Portal seine Icons aus `public/icons/<slug>.svg`.
|
|
|
|
## Neues Tool ins Portal aufnehmen
|
|
|
|
1. In Authentik eine Application anlegen (Name, Slug, Launch-URL). Als Launch-URL den **SSO-Startpunkt** der App nehmen (z.B. `https://vvl.handy-insel.de/auth/login`), nicht die Startseite. Dann ist man nach dem Klick auf die Kachel direkt angemeldet, statt erst auf der Anmeldeseite der App zu landen. Die Launch-URLs der eigenen Tools stehen in `LAUNCH_URLS` im Setup-Skript.
|
|
2. Slug in `APP_ACCESS` eintragen und `scripts/authentik-setup.py --apply` laufen lassen (oder die Gruppe in Authentik direkt binden).
|
|
3. Optional: Icon `public/icons/<slug>.svg` und Reihenfolge in `APP_ORDER`.
|
|
4. Kopfleiste im Tool einbinden (siehe unten).
|
|
|
|
## Kopfleiste in ein Tool einbauen
|
|
|
|
```html
|
|
<script src="https://portal.handy-insel.de/topbar.js" data-app="vvl" defer></script>
|
|
```
|
|
|
|
- `data-app`: Slug der Authentik-Application. Die App wird im Menü markiert und ihr Name neben dem Logo angezeigt.
|
|
- `data-sticky`: Die Leiste bleibt beim Scrollen oben.
|
|
- `data-auto="false"`: nicht automatisch einfügen, sondern selbst `<hi-topbar app="vvl"></hi-topbar>` platzieren.
|
|
- Die Leiste ist 44 px hoch und setzt `--hi-topbar-height` auf `<html>`. Layouts mit `height: 100vh` sollten `calc(100vh - var(--hi-topbar-height, 0px))` verwenden.
|
|
- Die Leiste nutzt Shadow DOM, CSS-Konflikte mit dem Tool sind also ausgeschlossen.
|
|
- **Eigene Knöpfe** (z.B. Speichern, Filter, Hilfe) erscheinen rechts vor dem Avatar, wenn sie als Kind mit `slot="aktionen"` in `<hi-topbar>` stehen. Die Leiste ist dunkelviolett, Text erbt Weiß; das Styling der Knöpfe bleibt beim Tool.
|
|
|
|
```html
|
|
<!-- mit data-auto="false" selbst platziert -->
|
|
<hi-topbar app="vvl">
|
|
<button slot="aktionen" class="mein-knopf">Speichern</button>
|
|
</hi-topbar>
|
|
```
|
|
|
|
```js
|
|
// oder in die automatisch eingefügte Leiste hängen (nach DOMContentLoaded, in React z.B. im useEffect)
|
|
const btn = document.createElement('button');
|
|
btn.slot = 'aktionen';
|
|
btn.textContent = 'Speichern';
|
|
document.querySelector('hi-topbar')?.append(btn);
|
|
```
|
|
|
|
- **Eigene Einträge im Benutzer-Menü** (z.B. „Meine Einstellungen“, Sitzungsanzeige) mit `slot="benutzer"`. Sie stehen zwischen Name/E-Mail und den Links. Stellt ein Tool dort etwas ein, erscheint das Benutzer-Menü auch ohne Portal-Sitzung (dann mit Personen-Symbol statt Initialen), etwa nach einer Anmeldung im Tool ohne SSO. Klickt man auf einen Link darin, schließt sich das Menü.
|
|
|
|
### Abmelden aus dem Tool heraus
|
|
|
|
Vor dem Abmelden feuert die Leiste das Ereignis `hi-abmelden` (bubbles, composed, cancelable). Das Tool beendet darin seine eigene Sitzung, so gibt es genau ein „Abmelden“:
|
|
|
|
```js
|
|
document.addEventListener('hi-abmelden', (e) => {
|
|
// Leiste wartet auf das Promise, höchstens 3 Sekunden
|
|
e.detail.warten(fetch('/api/logout', { method: 'POST', credentials: 'include' }));
|
|
});
|
|
```
|
|
|
|
Danach geht es zum Abmelden bei Portal und Authentik. Ohne Portal-Sitzung lädt die Leiste stattdessen die Seite neu, dann zeigt das Tool seine eigene Anmeldung. Mit `e.preventDefault()` übernimmt das Tool die Weiterleitung komplett selbst.
|
|
|
|
**Content-Security-Policy des Tools erweitern** (sonst blockiert der Browser die Leiste):
|
|
|
|
```
|
|
script-src … https://portal.handy-insel.de
|
|
connect-src … https://portal.handy-insel.de
|
|
img-src … https://portal.handy-insel.de https://login.handy-insel.de
|
|
```
|
|
|
|
Ist man im Tool angemeldet, im Portal aber nicht, zeigt das Menü „Jetzt anmelden“. Weil die Authentik-Sitzung schon besteht, ist das ein unsichtbarer Redirect zurück ins Tool.
|
|
|
|
## Abmelden
|
|
|
|
„Abmelden“ in der Kopfleiste läuft so ab:
|
|
|
|
1. Die Leiste feuert `hi-abmelden`, das Tool beendet seine eigene Sitzung (siehe oben).
|
|
2. Weiter zu `portal/auth/logout?returnTo=<aktuelle Seite>`: Die Portal-Sitzung endet, der Rücksprung wird für 5 Minuten in einem Cookie gemerkt (nur erlaubte Domains).
|
|
3. Weiter zum End-Session-Endpunkt von Authentik. Der Provider „Portal“ nutzt den Abmelde-Ablauf **`default-invalidation-flow`**, der auch die Authentik-Sitzung beendet. Der übliche `default-provider-invalidation-flow` würde nur beim Portal abmelden, man wäre beim nächsten Klick sofort wieder drin.
|
|
4. Authentik schickt zurück auf `portal/`, das Portal leitet weiter zur gemerkten Seite. Dort zeigt das Tool seine Anmeldeseite.
|
|
|
|
Das funktioniert auch, wenn die Portal-Sitzung schon abgelaufen ist. „Abmelden“ im Portal selbst macht Schritt 2 bis 4 und landet auf der Authentik-Anmeldung.
|
|
|
|
Tools, die nicht auf `hi-abmelden` hören, behalten ihre eigene Sitzung bis zu deren Ablauf. Mit beendeter Authentik-Sitzung kann man sich dort aber nicht neu per SSO anmelden.
|
|
|
|
## Betrieb
|
|
|
|
| | |
|
|
|---|---|
|
|
| Host | CT `portal` (192.168.1.28), Debian 13 |
|
|
| Dienst | `systemctl status hi-portal`, Logs: `journalctl -u hi-portal -f` |
|
|
| Code | `/opt/hi-portal` |
|
|
| Konfiguration | `/etc/hi-portal/portal.env` (siehe `.env.example`) |
|
|
| Daten | `/var/lib/hi-portal/portal.db` (Sitzungen, Aktuelles) |
|
|
| Pangolin | `portal.handy-insel.de` → `http://192.168.1.28:3010`. **Pangolin-eigene Authentifizierung für diese Ressource aus**, das Portal macht den Login selbst. |
|
|
|
|
**Erstinstallation**
|
|
|
|
```bash
|
|
# 1. CT vorbereiten (einmalig)
|
|
scp deploy/setup-ct.sh root@192.168.1.28: && ssh root@192.168.1.28 bash setup-ct.sh
|
|
# 2. Portal ausspielen
|
|
./deploy/deploy.sh root@192.168.1.28
|
|
# 3. Authentik einrichten (Probelauf, dann ausführen); schreibt Client-ID/Secret/Token in die portal.env
|
|
ssh root@192.168.1.28
|
|
AK_TOKEN=<admin-token> python3 /opt/hi-portal/scripts/authentik-setup.py
|
|
AK_TOKEN=<admin-token> python3 /opt/hi-portal/scripts/authentik-setup.py --apply --env /etc/hi-portal/portal.env
|
|
systemctl restart hi-portal
|
|
```
|
|
|
|
**Update:** `./deploy/deploy.sh`
|
|
|
|
## Entwicklung
|
|
|
|
```bash
|
|
npm install
|
|
npm run build
|
|
# Server ohne Authentik-Login (nur lokal, in production gesperrt):
|
|
NODE_ENV=development DEV_USER=kerim SESSION_SECRET=dev AUTHENTIK_TOKEN=<token> node dist/server/index.js
|
|
# Frontend mit Hot-Reload auf :5180 (leitet /api und /auth an :3010 weiter)
|
|
npm run dev:web
|
|
```
|
|
|
|
Aufbau: `server/` (Express + TypeScript, openid-client, SQLite), `frontend/` (React + Vite + Tailwind), `topbar/` (Web-Component, per esbuild gebaut), `public/` (Icons, Logo).
|