Der Wartungsplaner steht als offener Punkt drin, nicht als Haken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
428 lines
22 KiB
Markdown
428 lines
22 KiB
Markdown
# Planer
|
||
|
||
Aufgaben, Ideen, Projekte und Zeitblöcke an einem Ort.
|
||
|
||
Die Kernidee: Kalender aus Microsoft 365 (Firma, Gewerbe) und Google (privat)
|
||
lesen und Aufgaben per Ziehen und Ablegen als Zeitblöcke direkt in diese
|
||
Kalender schreiben — inklusive Rückweg, wenn ein Block im Kalender verschoben
|
||
wird.
|
||
|
||
> **Stand:** Meilenstein 4 von 8. Die Kernidee steht: Aufgaben lassen sich
|
||
> per Ziehen und Ablegen als Zeitblöcke in den Kalender schreiben, und
|
||
> Verschiebungen von dort kommen zurück. Es folgen Benachrichtigungen,
|
||
> Integrationen, Schnellerfassung vom Handy und ein MCP-Server. Der
|
||
> [Fahrplan](ROADMAP.md) sagt, was wann folgt.
|
||
|
||
- **[Schnellstart](docs/SCHNELLSTART.md)** – in zehn Minuten von `git clone`
|
||
zur ersten Anmeldung
|
||
- **[Handbuch](docs/handbuch/)** – Anleitung für den Alltag
|
||
- **[Fahrplan](ROADMAP.md)** – was geplant ist und was bewusst nicht
|
||
- **[Änderungen](CHANGELOG.md)** – was sich je Version getan hat
|
||
|
||
## Funktionsumfang
|
||
|
||
### Heute
|
||
|
||
- **Schnellerfassung** mit einem einzigen Feld; alles ohne Bereich sammelt
|
||
sich in der **Inbox** und wird später einsortiert
|
||
- **Aufgaben** mit Typ, Status, Priorität, Aufwand und Fälligkeit, sortiert
|
||
nach Dringlichkeit und filterbar über die Adresszeile
|
||
- **Ideen** getrennt von Aufgaben — ohne Termin, per Klick übernehmbar
|
||
- **Projekte** mit Arbeitspaketen, Meilensteinen und Fortschritt
|
||
- **Kalender lesen** aus Microsoft 365 und Google; die Startseite zeigt je
|
||
Bereich, wie voll der heutige Tag schon ist
|
||
- **Wochenplanung** per Ziehen und Ablegen: Aufgaben werden zu echten
|
||
Kalenderterminen, Verschiebungen im Kalender kommen zurück
|
||
- **Benachrichtigungen** per Web-Push, E-Mail und Telegram: Tagesüberblick,
|
||
fällige Aufgaben, Vorlauf vor einem Zeitblock — mit Ruhezeiten und ab Werk
|
||
still
|
||
- **Integrationen**: Zammad-Tickets werden zu Aufgaben, Telegram erfasst und
|
||
hakt ab, und der Spiegel bringt dienstliche Belegung nach Hause — ohne
|
||
Titel
|
||
- Anmeldung über einen OIDC-Anbieter, über Passkeys oder mit E-Mail und
|
||
Passwort — je Instanz einzeln zuschaltbar
|
||
- Rollen `Benutzer` und `Verwaltung`; jede schreibende Aktion wird
|
||
serverseitig geprüft und hinterlässt einen Protokolleintrag
|
||
- Bereiche mit Farbe, Arbeitsfenster und Tageskapazität je Wochentag
|
||
- Hell und Dunkel, Oberfläche durchgehend auf Deutsch
|
||
- Betrieb über Docker Compose, mit Sicherung, Aktualisierung und Healthcheck
|
||
|
||
### Geplant
|
||
|
||
Schnellerfassung vom Handy, eine öffentliche API und ein MCP-Server.
|
||
Siehe [ROADMAP.md](ROADMAP.md).
|
||
|
||
## Bildergalerie
|
||
|
||
| | Hell | Dunkel |
|
||
| ------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||
| Anmelden |  |  |
|
||
| Start |  |  |
|
||
| Inbox |  |  |
|
||
| Woche |  |  |
|
||
| Aufgaben |  |  |
|
||
| Projekte |  |  |
|
||
| Ideen |  |  |
|
||
| Kalender |  |  |
|
||
| Bereiche |  |  |
|
||
| Einstellungen |  |  |
|
||
| Meldungen |  |  |
|
||
| Integrationen |  |  |
|
||
| Protokoll |  |  |
|
||
|
||
Alle Bilder entstehen mit `npm run screenshots` in einer Wegwerf-Instanz mit
|
||
ausschließlich erfundenen Daten — niemals aus einer Instanz mit echten Daten.
|
||
|
||
## Die zwei Instanzen
|
||
|
||
Ein Abbild, zwei Deployments, gesteuert über `INSTANZ`. Die Umschaltung wird
|
||
**serverseitig erzwungen**: Was eine Instanz nicht darf, liefert dort 404 und
|
||
lädt seine Konfiguration gar nicht erst.
|
||
|
||
| | `firma` | `daheim` |
|
||
| -------------- | -------------------------------------- | ------------------------------ |
|
||
| Anmeldung | OIDC Entra ID | OIDC authentik |
|
||
| Kalenderkonten | Graph (Firmen-Tenant) | Graph (Gewerbe-Tenant), Google |
|
||
| Mail | Graph Firmenpostfach | Graph Gewerbepostfach, Gmail |
|
||
| Integrationen | Zammad, Wartungsplaner, Spiegel senden | Telegram, Spiegel empfangen |
|
||
|
||
Ein falsch geschriebener Wert bricht den Start ab, statt still auf einen
|
||
Standard zu fallen — in der Firma dürfen keine Provider anlaufen, die dort
|
||
nichts zu suchen haben.
|
||
|
||
## Umgebungsvariablen
|
||
|
||
Alle Werte stehen in `.env`. Vorlage: [`.env.example`](.env.example).
|
||
`.env` gehört niemals ins Git.
|
||
|
||
### Pflicht
|
||
|
||
| Variable | Beispiel | Bedeutung |
|
||
| ----------------- | ---------------------------------------------------- | ---------------------------------------------- |
|
||
| `INSTANZ` | `daheim` | `firma` oder `daheim` |
|
||
| `APP_URL` | `https://planer.example.de` | Öffentliche Adresse, ohne Schrägstrich am Ende |
|
||
| `DATABASE_URL` | `postgresql://planer:…@db:5432/planer?schema=public` | Verbindung zur Datenbank |
|
||
| `AUTH_SECRET` | _(32 Byte Base64)_ | Signiert Sitzungen. `openssl rand -base64 32` |
|
||
| `WEBAUTHN_RP_ID` | `planer.example.de` | Nackte Domain, ohne Schema und Port |
|
||
| `WEBAUTHN_ORIGIN` | `https://planer.example.de` | Exakt die Herkunft im Browser |
|
||
|
||
### Datenbank im Compose-Stapel
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
| ------------------- | ---------- | ---------------------------------------------- |
|
||
| `POSTGRES_USER` | `planer` | Benutzer der Datenbank |
|
||
| `POSTGRES_PASSWORD` | _(keiner)_ | Pflicht. Muss zu `DATABASE_URL` passen |
|
||
| `POSTGRES_DB` | `planer` | Name der Datenbank |
|
||
| `POSTGRES_PORT` | `5432` | Nach außen veröffentlichter Port |
|
||
| `APP_PORT` | `3000` | Nach außen veröffentlichter Port der Anwendung |
|
||
|
||
### Anmeldung
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
| -------------------- | -------- | ------------------------------------------ |
|
||
| `AUTH_TRUST_HOST` | `false` | Hinter einem Reverse Proxy auf `true` |
|
||
| `ANMELDUNG_PASSWORT` | `false` | Anmeldung mit E-Mail und Passwort erlauben |
|
||
| `OIDC_NAME` | _(leer)_ | Beschriftung des Anmeldeknopfs |
|
||
| `OIDC_ISSUER` | _(leer)_ | Issuer-URL des Anbieters |
|
||
| `OIDC_CLIENT_ID` | _(leer)_ | Client-ID |
|
||
| `OIDC_CLIENT_SECRET` | _(leer)_ | Client-Secret |
|
||
| `WEBAUTHN_RP_NAME` | `Planer` | Name, den das Gerät beim Passkey anzeigt |
|
||
|
||
OIDC ist aktiv, sobald Issuer, Client-ID und Secret gesetzt sind. Der
|
||
Rückleit-URI beim Anbieter lautet `<APP_URL>/api/auth/callback/oidc`.
|
||
|
||
### Kalenderanbieter
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
| ---------------------- | --------------- | ------------------------------------------------------------- |
|
||
| `GRAPH_CLIENT_ID` | _(leer)_ | Anwendungs-ID der App-Registrierung in Entra ID |
|
||
| `GRAPH_CLIENT_SECRET` | _(leer)_ | Geheimnis dieser Registrierung |
|
||
| `GRAPH_TENANT_ID` | `organizations` | Tenant-ID, oder `organizations` für jeden Arbeitsplatz-Tenant |
|
||
| `GOOGLE_CLIENT_ID` | _(leer)_ | OAuth-Client-ID aus der Google Cloud Console |
|
||
| `GOOGLE_CLIENT_SECRET` | _(leer)_ | Geheimnis dieses Clients |
|
||
|
||
Ein Anbieter erscheint erst, wenn Kennung **und** Geheimnis gesetzt sind —
|
||
und nur, wenn die Instanz ihn erlaubt (Google nur `daheim`).
|
||
|
||
### Benachrichtigungen
|
||
|
||
Beides ist freiwillig. Fehlt ein Satz, bleibt der Weg in den Einstellungen
|
||
ausgegraut, und der Planer läuft ohne ihn weiter.
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
| ------------------- | -------- | --------------------------------------------------------- |
|
||
| `VAPID_OEFFENTLICH` | _(leer)_ | Öffentlicher Push-Schlüssel, geht an den Browser |
|
||
| `VAPID_PRIVAT` | _(leer)_ | Privater Push-Schlüssel, bleibt auf dem Server |
|
||
| `VAPID_KONTAKT` | _(leer)_ | Kontakt für den Push-Dienst, `mailto:` oder `https:` |
|
||
| `SMTP_HOST` | _(leer)_ | Mailserver. Ohne ihn kein Mailversand |
|
||
| `SMTP_PORT` | `587` | Port des Mailservers |
|
||
| `SMTP_BENUTZER` | _(leer)_ | Anmeldename. Leer lassen für einen Server ohne Anmeldung |
|
||
| `SMTP_PASSWORT` | _(leer)_ | Passwort dazu |
|
||
| `SMTP_ABSENDER` | _(leer)_ | Absenderadresse, z. B. `Planer <planer@example.de>` |
|
||
| `SMTP_TLS` | `false` | `true` bei Port 465 (SMTPS), `false` bei 587 mit STARTTLS |
|
||
|
||
Push braucht **alle drei** VAPID-Werte, Mail mindestens `SMTP_HOST` und
|
||
`SMTP_ABSENDER`. Die Schlüssel entstehen einmalig mit:
|
||
|
||
```bash
|
||
npx web-push generate-vapid-keys
|
||
```
|
||
|
||
Wird das Schlüsselpaar später getauscht, müssen sich alle Geräte neu
|
||
anmelden — die alten Abos sind dann wertlos. Push setzt außerdem HTTPS
|
||
voraus; `http://localhost` gilt dem Browser als sicher, jede andere
|
||
Klartextadresse nicht.
|
||
|
||
Was der Planer wann meldet, steht im
|
||
[Handbuch](docs/handbuch/benachrichtigungen.md).
|
||
|
||
### Integrationen
|
||
|
||
Welche Integration eine Instanz überhaupt darf, entscheidet `INSTANZ` — nicht
|
||
die Konfiguration. Ein Zammad-Token in der Instanz `daheim` bleibt wirkungslos.
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
| ------------------- | ------------------- | ---------------------------------------------------------------- |
|
||
| `ZAMMAD_URL` | _(leer)_ | Adresse der Zammad-Instanz, nur `firma` |
|
||
| `ZAMMAD_TOKEN` | _(leer)_ | Token aus **Profil → Token-Zugriff** |
|
||
| `ZAMMAD_ABFRAGE` | `owner_id:me AND …` | Suchausdruck; was darauf passt, wird zur Aufgabe |
|
||
| `TELEGRAM_TOKEN` | _(leer)_ | Bot-Token von [@BotFather](https://t.me/BotFather), nur `daheim` |
|
||
| `SPIEGEL_GEHEIMNIS` | _(leer)_ | Gemeinsames Geheimnis beider Instanzen |
|
||
| `SPIEGEL_ZIEL` | _(leer)_ | Adresse der empfangenden Instanz; nur auf der sendenden Seite |
|
||
|
||
**Zammad** liest nur. Ein Ticket schließt man im Ticketsystem — sonst weiß die
|
||
Kollegin nicht, warum es plötzlich zu ist. Umgekehrt schließt der Planer eine
|
||
Aufgabe, sobald Zammad das Ticket als erledigt meldet.
|
||
|
||
**Telegram** braucht keinen Webhook und damit keine von außen erreichbare
|
||
Adresse. Welche Chats mitreden dürfen, wird in der Anwendung unter
|
||
**Integrationen** gekoppelt: Knopf drücken, Code an den Bot schicken, fertig.
|
||
Nachrichten aus unbekannten Chats werden verworfen, ohne zu antworten.
|
||
|
||
**Der Spiegel** geht in eine Richtung: `firma` sendet, `daheim` empfängt.
|
||
Hinüber gehen Belegungen **ohne Titel** — daheim ist zu sehen, _dass_ die Zeit
|
||
weg ist, nicht woran gearbeitet wird — und jene Aufgaben, die einzeln zum
|
||
Spiegeln markiert wurden. Jede Sendung ist mit dem gemeinsamen Geheimnis
|
||
signiert und älter als fünf Minuten wertlos.
|
||
|
||
Das Geheimnis erzeugst du wie das Sitzungsgeheimnis:
|
||
|
||
```bash
|
||
openssl rand -base64 32
|
||
```
|
||
|
||
Was der Alltag damit anfängt, steht im
|
||
[Handbuch](docs/handbuch/integrationen.md).
|
||
|
||
### Einrichtung und Sonstiges
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
| ---------------- | --------------- | ---------------------------------------------------------------------------------------- |
|
||
| `ADMIN_EMAIL` | _(leer)_ | Erstes Verwaltungskonto, wird vom Seed angelegt |
|
||
| `ADMIN_PASSWORT` | _(leer)_ | Mindestens 12 Zeichen. Ohne den Wert entsteht ein Konto, das nur OIDC oder Passkey kennt |
|
||
| `TZ` | `Europe/Berlin` | Zeitzone für Anzeigen und Tagesgrenzen |
|
||
| `DEMO_MODUS` | `false` | Nur für die Screenshot-Instanz. Im Betrieb niemals `true` |
|
||
|
||
## Kalenderanbieter einrichten
|
||
|
||
Beides ist einmalige Handarbeit beim Anbieter. Der Planer fragt Lese- **und**
|
||
Schreibrechte an: ab M4 schreibt er Zeitblöcke zurück, und eine zweite
|
||
Zustimmungsrunde für jedes Konto wäre lästiger als eine etwas breitere
|
||
Berechtigung von Anfang an.
|
||
|
||
### Microsoft 365 (Entra ID)
|
||
|
||
1. <https://entra.microsoft.com> → **Identität → Anwendungen →
|
||
App-Registrierungen → Neue Registrierung**.
|
||
2. Name frei wählen, etwa „Planer".
|
||
3. Unterstützte Kontotypen: **Nur Konten in diesem Organisationsverzeichnis**
|
||
(dann `GRAPH_TENANT_ID` auf die Tenant-ID setzen) oder
|
||
**Konten in einem beliebigen Organisationsverzeichnis** (dann
|
||
`organizations` belassen).
|
||
4. Umleitungs-URI, Plattform **Web**:
|
||
`<APP_URL>/api/kalender/rueckleitung/graph`
|
||
5. Nach dem Anlegen: **Anwendungs-ID (Client)** nach `GRAPH_CLIENT_ID`,
|
||
**Verzeichnis-ID (Mandant)** nach `GRAPH_TENANT_ID`.
|
||
6. **Zertifikate & Geheimnisse → Neuer geheimer Clientschlüssel**. Den Wert
|
||
sofort nach `GRAPH_CLIENT_SECRET` kopieren — er wird nur einmal angezeigt.
|
||
7. **API-Berechtigungen → Berechtigung hinzufügen → Microsoft Graph →
|
||
Delegierte Berechtigungen**: `Calendars.ReadWrite`, `offline_access`,
|
||
`openid`, `email`.
|
||
|
||
> Ist im Tenant die Zustimmung durch Endbenutzer abgeschaltet, muss ein
|
||
> Administrator einmalig **Administratorzustimmung erteilen** klicken.
|
||
|
||
### Google Kalender
|
||
|
||
1. <https://console.cloud.google.com> → Projekt anlegen oder wählen.
|
||
2. **APIs & Dienste → Bibliothek** → _Google Calendar API_ aktivieren.
|
||
3. **OAuth-Zustimmungsbildschirm** einrichten. Für den Eigengebrauch genügt
|
||
_Extern_ mit Veröffentlichungsstatus _Test_ und der eigenen Adresse als
|
||
Testbenutzer.
|
||
4. Bereiche (Scopes) hinzufügen:
|
||
`.../auth/calendar.events` und `.../auth/calendar.readonly`.
|
||
5. **Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-ID**,
|
||
Anwendungstyp **Webanwendung**.
|
||
6. Autorisierter Weiterleitungs-URI:
|
||
`<APP_URL>/api/kalender/rueckleitung/google`
|
||
7. Client-ID und Clientschlüssel nach `GOOGLE_CLIENT_ID` und
|
||
`GOOGLE_CLIENT_SECRET`.
|
||
|
||
> Bleibt das Projekt im Status _Test_, laufen die Aktualisierungstoken nach
|
||
> sieben Tagen ab und die Konten müssen neu verbunden werden. Für den
|
||
> Dauerbetrieb den Zustimmungsbildschirm **veröffentlichen**.
|
||
|
||
Nach dem Eintragen: `docker compose up -d` und im Planer unter **Kalender**
|
||
verbinden. Die Anleitung für den Alltag steht im
|
||
[Handbuch](docs/handbuch/kalenderkonten.md).
|
||
|
||
## Betrieb
|
||
|
||
### Starten und stoppen
|
||
|
||
```bash
|
||
docker compose up -d # starten
|
||
docker compose logs -f app # Protokoll mitlesen
|
||
docker compose down # stoppen, Daten bleiben
|
||
```
|
||
|
||
Der Stapel besteht aus drei Diensten: `db`, ein Einmaldienst `migration`, der
|
||
die Migrationen einspielt und sich beendet, und `app`.
|
||
|
||
### Healthcheck
|
||
|
||
```bash
|
||
curl https://planer.example.de/api/health
|
||
```
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"version": "0.1.0",
|
||
"instanz": "daheim",
|
||
"datenbank": "ok",
|
||
"zeit": "2026-03-17T08:20:00.000Z"
|
||
}
|
||
```
|
||
|
||
Die Auskunft ist bewusst ohne Anmeldung erreichbar und enthält deshalb keine
|
||
Zählstände, Namen oder Adressen. Bei `"status": "fehler"` antwortet der
|
||
Endpunkt mit HTTP 503.
|
||
|
||
### Aktualisieren
|
||
|
||
```bash
|
||
./update.sh # auf den neuesten Tag
|
||
./update.sh --nur-pruefen # nur zeigen, was passieren würde
|
||
./update.sh --version v0.2.0 # auf eine bestimmte Version
|
||
./update.sh --ja # ohne Rückfrage
|
||
```
|
||
|
||
Das Skript prüft die Voraussetzungen, zeigt die Änderungen aus dem CHANGELOG,
|
||
legt eine Sicherung an, wechselt auf den Ziel-Tag, baut, startet und wartet
|
||
auf einen gesunden `/api/health` mit der erwarteten Version. Klappt das nicht,
|
||
rollt es auf die vorherige Version zurück und nennt den Pfad der Sicherung.
|
||
Jeder Lauf wird nach `logs/update-<Datum>.log` protokolliert.
|
||
|
||
Die **Datenbank wird beim Zurückrollen nicht automatisch zurückgespielt** —
|
||
das wäre bei einem Teilfehler gefährlicher als hilfreich. Das Skript nennt den
|
||
Befehl dafür.
|
||
|
||
### Sicherung und Wiederherstellung
|
||
|
||
```bash
|
||
./backup.sh # Datenbank, .env und Uploads
|
||
./backup.sh --tage 90 # längere Aufbewahrung
|
||
./backup.sh --nur-datenbank # nur der Dump
|
||
```
|
||
|
||
Je Lauf entstehen zwei Dateien in `sicherungen/`:
|
||
ein `pg_dump` im Custom-Format und ein tar.gz mit `.env` und `uploads`.
|
||
|
||
Wiederherstellen:
|
||
|
||
```bash
|
||
# Datenbank
|
||
docker compose exec -T db pg_restore -U planer -d planer --clean --if-exists \
|
||
< sicherungen/planer-db-2026-03-17_02-00-00.dump
|
||
|
||
# Dateien
|
||
tar -xzf sicherungen/planer-dateien-2026-03-17_02-00-00.tar.gz
|
||
```
|
||
|
||
Die Sicherung enthält `.env` und damit alle Geheimnisse. Sie gehört so
|
||
geschützt wie die Datenbank selbst.
|
||
|
||
Nächtlich per cron:
|
||
|
||
```cron
|
||
0 2 * * * cd /opt/planer && ./backup.sh --tage 30 >> logs/backup.log 2>&1
|
||
```
|
||
|
||
## Entwicklung
|
||
|
||
```bash
|
||
npm install
|
||
docker compose up -d db # nur die Datenbank
|
||
npm run db:migrieren # Migrationen einspielen
|
||
npm run db:seed # Bereiche und Verwaltungskonto
|
||
npm run dev # Entwicklungsserver
|
||
```
|
||
|
||
### Prüfungen
|
||
|
||
```bash
|
||
npm run pruefen # Prettier, ESLint, tsc, Vitest, ShellCheck
|
||
npm run test:integration # gegen eine echte Datenbank
|
||
npm run test:migration # Migrationen gegen eine Wegwerf-Datenbank
|
||
npm run test:e2e # Playwright gegen die gebaute Anwendung
|
||
npm run screenshots # Handbuchbilder neu aufnehmen
|
||
```
|
||
|
||
`npm run pruefen` muss vor jedem Commit grün sein. Fehlt `shellcheck` lokal,
|
||
wird das Docker-Abbild `koalaman/shellcheck` benutzt.
|
||
|
||
### Aufbau
|
||
|
||
```
|
||
prisma/ Schema, Migrationen, Seed, Demo-Seed
|
||
scripts/ Prüf-, Test- und Betriebsskripte
|
||
src/app/ Seiten und API-Routen; (app) = angemeldeter Bereich
|
||
src/components/ Bausteine; ui/ enthält die Primitiven
|
||
src/lib/ Reine Logik ohne DB und ohne Systemzeit, je Datei ein Test
|
||
src/server/ DB-nahe Dienste, Server Actions, Provider
|
||
e2e/ Playwright-Rauchtests
|
||
screenshots/ Aufnahme der Handbuchbilder
|
||
docs/ Schnellstart, Handbuch, Bilder
|
||
devlog/ Entwicklungstagebuch und Zeiterfassung
|
||
```
|
||
|
||
Reine Funktionen bekommen die aktuelle Zeit als Parameter, damit sie ohne
|
||
Tricks prüfbar bleiben. Die Tests laufen bewusst in UTC — so fällt sofort auf,
|
||
wenn irgendwo die Ortszeit des Servers durchschlägt statt Europe/Berlin.
|
||
|
||
### Bekannte Meldungen
|
||
|
||
`npm audit` meldet vier Funde in transitiven Abhängigkeiten der
|
||
**`prisma`-CLI** (`deepmerge-ts` über `@prisma/config`, `mysql2`). Beide sind
|
||
Entwicklungsabhängigkeiten und erreichen das Laufzeitabbild nicht; MySQL wird
|
||
ohnehin nie geladen. Der angebotene Fix wäre ein Rückschritt auf Prisma 6.
|
||
|
||
## Sicherheit
|
||
|
||
- Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich, damit
|
||
sich nicht abfragen lässt, wer hier ein Konto hat.
|
||
- Sitzungen sind JWT, aber Rolle und Sperrstatus werden bei jeder Anfrage
|
||
frisch aus der Datenbank gelesen — eine Sperre wirkt sofort.
|
||
- Jede schreibende Aktion prüft die Rolle in der Seite **und** noch einmal in
|
||
der Server Action. Die Oberfläche blendet Knöpfe nur zusätzlich aus.
|
||
- Audit-Details werden vor dem Speichern gesäubert: Felder, deren Name nach
|
||
Passwort, Token, Betreff oder Mailinhalt aussieht, werden ersetzt.
|
||
- Keine Geheimnisse, Tokens, Mailinhalte oder Termin-Betreffs in Protokollen.
|
||
|
||
Gefundene Schwachstellen bitte nicht als Issue anlegen, sondern direkt melden.
|
||
|
||
## Lizenz
|
||
|
||
Privates Projekt, keine Lizenz zur Weiterverwendung.
|