docs: document phase 2 decisions and update roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
JannisandClaude Opus 5.5 committed 2026-10-09 18:54:02 +02:00
1 parent 045ca38931
commit ccf3aea7f2
7 files changed
+146 -35

No files matched your search

+5 -1
View File
@@ -43,7 +43,11 @@ Lokaler Seed-Admin (AppHost-Parameter, siehe `src/FamilyPlanner.AppHost/appsetti
* Commits: Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`, `ci:`). * Commits: Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`, `ci:`).
* Alle Zeiten in UTC speichern (`DateTimeOffset`/UTC), Anzeige in Europe/Berlin. `TimeProvider` injizieren statt `DateTime.UtcNow`. * Alle Zeiten in UTC speichern (`DateTimeOffset`/UTC), Anzeige in Europe/Berlin. `TimeProvider` injizieren statt `DateTime.UtcNow`.
* Neue Entitäten erben von `AuditableEntity`; löschbare implementieren `ISoftDeletable`; Einkauf/Aufgaben/Termine bekommen `RowVersion`. * Neue Entitäten erben von `AuditableEntity`; löschbare implementieren `ISoftDeletable`; Einkauf/Aufgaben/Termine bekommen `RowVersion`.
* Berechtigungen immer serverseitig über Policies (`RequireAuthorization(Policies.X)`), nie nur im UI. Zu jeder Policy Unit-Tests. * Berechtigungen immer serverseitig, nie nur im UI: fachliche Endpunkte mit `.RequirePermission(Permission.X)` (Matrix in
`Domain/Authorization/Permissions.cs`, ADR-0008). Bei `AccessLevel.Restricted` muss der Slice objektbezogen prüfen
(z. B. „nur eigene“) – dafür Tests schreiben. Matrix-Änderungen in `PermissionMatrixTests` nachziehen.
* Administrative Aktionen schreiben ins Audit-Log (`IAuditLogger`), ohne Passwörter/PINs.
* Request-DTOs mit Validatoren in `FamilyPlanner.Shared` (FluentValidation) – Api validiert per `.Validate<T>()`, der Client nutzt dieselben Validatoren.
* Schreibende Requests vom Client tragen `X-Requested-With: FamilyPlanner` (wird vom `HttpClient`-Handler im Client gesetzt, siehe ADR-0006). * Schreibende Requests vom Client tragen `X-Requested-With: FamilyPlanner` (wird vom `HttpClient`-Handler im Client gesetzt, siehe ADR-0006).
* Validierung mit FluentValidation im jeweiligen Slice. * Validierung mit FluentValidation im jeweiligen Slice.
* Pakete nur über `Directory.Packages.props` (keine Versionen in `.csproj`), nur stabile Versionen. * Pakete nur über `Directory.Packages.props` (keine Versionen in `.csproj`), nur stabile Versionen.
+6 -2
View File
@@ -3,7 +3,8 @@
Digitaler Familienplaner als Progressive Web App: Einkaufsliste, Essenplan, Aufgaben & Ämtchen, Familienkalender Digitaler Familienplaner als Progressive Web App: Einkaufsliste, Essenplan, Aufgaben & Ämtchen, Familienkalender
(inkl. Abo für iPhone/Outlook) und Chat – für Smartphone, Tablet und Desktop. (inkl. Abo für iPhone/Outlook) und Chat – für Smartphone, Tablet und Desktop.
> **Status:** Phase 1 (Fundament) ist umgesetzt: Login, Rollen, Navigation, PWA-Grundgerüst, Aspire- und Docker-Betrieb. > **Status:** Phase 1 (Fundament) und Phase 2 (Rollen & Admin) sind umgesetzt: Login, Rollenmatrix, Benutzerverwaltung,
> Familieneinstellungen, Audit-Log, Navigation, PWA-Grundgerüst, Aspire- und Docker-Betrieb.
> Die Fachfunktionen folgen phasenweise, siehe [`docs/ROADMAP.md`](docs/ROADMAP.md). > Die Fachfunktionen folgen phasenweise, siehe [`docs/ROADMAP.md`](docs/ROADMAP.md).
* Architektur: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) * Architektur: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
@@ -66,7 +67,10 @@ einem eigenen Reverse Proxy mit TLS.
| `REGISTRY`, `TAG` | optionales Image-Präfix und Tag | | `REGISTRY`, `TAG` | optionales Image-Präfix und Tag |
Weitere Einstellungen (alle Dienste) folgen der ASP.NET-Core-Konvention `Abschnitt__Schlüssel`, z. B. Weitere Einstellungen (alle Dienste) folgen der ASP.NET-Core-Konvention `Abschnitt__Schlüssel`, z. B.
`RateLimiting__LoginPermitLimit=10` (Api). `RateLimiting__LoginPermitLimit=10` (Api) oder `Features__PinLogin=true` (Api, schaltet den vorbereiteten PIN-Login für Kinder frei).
Benutzer, Familieneinstellungen (Familienname, Zeitzone, Essenplan, Mahlzeiten, Aufgaben-Kategorien, Punktesystem, Gast-Rechte)
und das Protokoll administrativer Aktionen verwaltet der Administrator in der App unter **Verwaltung**.
### Update ### Update
+35 -25
View File
@@ -79,38 +79,47 @@ Alle Zeitpunkte werden als UTC (`DateTimeOffset`/`DateTime` UTC) gespeichert und
| Bereich | Entitäten | | Bereich | Entitäten |
|---|---| |---|---|
| Identity | `AppUser` (DisplayName, Color, AvatarUrl, MustChangePassword, IsActive, ExpiresAt, PinHash), `AppRole` (Admin, Familie, Kind, Gast) | | Identity ✅ | `AppUser` (DisplayName, Color, AvatarUrl, MustChangePassword, IsActive, ExpiresAt, PinHash, LastLoginAt, Soft Delete), `AppRole` (Admin, Familie, Kind, Gast), `UserAvatar` (Bild ≤ 1 MB) |
| Einstellungen | `FamilySettings` (Name, TimeZone, MealPlanDays, MealTypes, PointsEnabled, ChildTasksNeedApproval, GuestCanEditShopping, GuestChatEnabled) | | Einstellungen ✅ | `FamilySettings` (FamilyName, TimeZoneId, MealPlanDays, MealTypes, TaskCategories, ChorePointsEnabled, ChildTasksRequireApproval, GuestsCanEditShoppingList, GuestChatEnabled) |
| Einkauf | `ShoppingList` (Name, IsDefault), `ShoppingItem` (Text, Quantity, Category, IsDone, DoneAt, SortOrder), `ShoppingSuggestion` (Text, UseCount) | | Einkauf | `ShoppingList` (Name, IsDefault), `ShoppingItem` (Text, Quantity, Category, IsDone, DoneAt, SortOrder), `ShoppingSuggestion` (Text, UseCount) |
| Essen | `MealPlanEntry` (Date, MealType, Dish, Note, RecipeUrl, Ingredients), `Dish` (Name, Ingredients – für Autovervollständigung), `MealWish` (von Kindern) | | Essen | `MealPlanEntry` (Date, MealType, Dish, Note, RecipeUrl, Ingredients), `Dish` (Name, Ingredients – für Autovervollständigung), `MealWish` (von Kindern) |
| Aufgaben | `TodoTask` (Title, Notes, DueAt, HasTime, Priority, Status, Category, IsPrivate, IsChore, Points, NeedsApproval, RecurrenceRule, SeriesId), `TaskAssignment`, `TaskChecklistItem`, `TaskCompletion` (wer/wann/genehmigt), `TaskCategory`, `PointsLedger` | | Aufgaben | `TodoTask` (Title, Notes, DueAt, HasTime, Priority, Status, Category, IsPrivate, IsChore, Points, NeedsApproval, RecurrenceRule, SeriesId), `TaskAssignment`, `TaskChecklistItem`, `TaskCompletion` (wer/wann/genehmigt), `TaskCategory`, `PointsLedger` |
| Kalender | `Calendar` (Name, Color), `CalendarPermission` (Rolle/Benutzer → Read/Write), `CalendarEvent` (Title, StartUtc, EndUtc, AllDay, Location, Notes, RRule, ExDates, Uid), `EventPersonTag`, `EventException` (Overrides), `IcsFeedToken` (Token-Hash, Scope, Owner, RevokedAt) | | Kalender | `Calendar` (Name, Color), `CalendarPermission` (Rolle/Benutzer → Read/Write), `CalendarEvent` (Title, StartUtc, EndUtc, AllDay, Location, Notes, RRule, ExDates, Uid), `EventPersonTag`, `EventException` (Overrides), `IcsFeedToken` (Token-Hash, Scope, Owner, RevokedAt) |
| Chat | `ChatChannel` (Family / Direct), `ChatMember` (LastReadAt), `ChatMessage` (Text, Attachment), `ChatReaction` | | Chat | `ChatChannel` (Family / Direct), `ChatMember` (LastReadAt), `ChatMessage` (Text, Attachment), `ChatReaction` |
| Betrieb | `AuditLogEntry` (Who, Action, Target, Details, At), `PushSubscription` (vorbereitet) | | Betrieb | `AuditLogEntry` ✅ (ActorName, Action, TargetType/Id/Name, Details, CreatedAt), `PushSubscription` (vorbereitet) |
## 4. Rollen- und Berechtigungsmodell ## 4. Rollen- und Berechtigungsmodell
Rollen: **Admin, Familie, Kind, Gast** (Konstanten in `FamilyPlanner.Domain.Roles`). Rollen: **Admin, Familie, Kind, Gast** (Konstanten in `FamilyPlanner.Domain.Roles`).
Berechtigungen werden als **Policies** formuliert (`FamilyPlanner.Domain.Authorization.Policies`) und Die Rollenmatrix der Anforderungen ist als reine Funktion `PermissionMatrix` im Domain-Projekt abgebildet (ADR-0008):
serverseitig per `RequireAuthorization(policy)` erzwungen. Feingranulare Regeln, die vom Objekt abhängen je `Permission` und Rolle eine Zugriffsstufe **Full (F)**, **Restricted (R)** = erreichbar, aber objektbezogene Prüfung im Slice,
(„nur eigene Einträge löschen“, „nur eigene Termine“), laufen über ressourcenbasierte Autorisierung oder **None (–)**. Die Api registriert je Permission eine Policy `Permission:<Name>`; Endpunkte nutzen `.RequirePermission(…)`.
(`IAuthorizationHandler` mit Ressource) bzw. reine Domänenfunktionen, die unit-getestet sind.
| Policy | Rollen | | Permission | Admin | Familie | Kind | Gast | Bedeutung von R |
|---|---| |---|---|---|---|---|---|
| `Authenticated` | alle aktiven Benutzer | | `DashboardView` | F | F | F | R | Gast: eingeschränkte Kacheln |
| `FamilyMember` | Admin, Familie, Kind | | `ShoppingRead` | F | F | F | F | |
| `Parent` (Familie verwalten) | Admin, Familie | | `ShoppingWrite` | F | F | F | – ¹ | |
| `Admin` | Admin | | `ShoppingDelete` | F | F | R | – | nur eigene Einträge |
| `ShoppingWrite` | Admin, Familie, Kind (+ Gast, falls in Einstellungen freigegeben) | | `MealPlanRead` | F | F | F | F | |
| `MealPlanEdit` | Admin, Familie | | `MealPlanEdit` | F | F | R | – | nur Wünsche vorschlagen |
| `MealWish` | Kind | | `TasksRead` | F | F | R | R | Kind: eigene + Familie; Gast: nur zugewiesene |
| `TaskAssign` | Admin, Familie | | `TasksEdit` | F | F | R | – | nur eigene, private Aufgaben |
| `ChatFamily` | Admin, Familie, Kind (+ Gast, falls freigegeben) | | `TasksAssign` | F | F | – | – | |
| `DirectMessages` | Admin, Familie, Kind | | `TasksCompleteAssigned` | F | F | F | F | |
| `CalendarRead` | F | F | F | R | nur freigegebene Kalender |
| `EventsEdit` | F | F | R | – | nur eigene Termine |
| `FamilyChat` | F | F | F | – ² | |
| `DirectMessages` | F | F | R | – | nur an Familienmitglieder |
| `UserManagement`, `CalendarManagement`, `SettingsManagement`, `AuditLogView` | F | – | – | – | |
Das UI blendet Funktionen anhand derselben Policy-Namen aus (`AuthorizeView Policy=…`), ist aber nie die einzige Sperre. ¹ `F`, wenn „Gäste dürfen die Einkaufsliste ergänzen/abhaken“ aktiviert ist. ² `F`, wenn „Gäste dürfen den Familienchat nutzen“ aktiviert ist.
Ein Benutzer mit `MustChangePassword = true` erhält nur Zugriff auf `/api/auth/*` (Policy `PasswordChanged` als Fallback-Policy).
Zusätzlich gibt es grobe Rollen-Policies (`Admin`, `Parent`, `FamilyMember`, `AnyRole`). Alle Policies – auch die
**Fallback-Policy** für jeden Endpunkt ohne eigene Angabe – verlangen: angemeldet, Konto nicht abgelaufen (Claim `fp:expires_at`)
und kein ausstehender Passwortwechsel. Ein Benutzer mit `MustChangePassword = true` erreicht nur `/api/auth/*`.
Das UI bekommt die wirksamen Berechtigungen über `/api/auth/me` und blendet anhand derselben Policy-Namen aus; es ist nie die einzige Sperre.
## 5. Authentifizierung ## 5. Authentifizierung
@@ -123,13 +132,14 @@ Ein Benutzer mit `MustChangePassword = true` erhält nur Zugriff auf `/api/auth/
* Seed-Admin aus Konfiguration (`Seed:AdminUserName`, `Seed:AdminPassword`, `Seed:AdminEmail`), `MustChangePassword = true`. * Seed-Admin aus Konfiguration (`Seed:AdminUserName`, `Seed:AdminPassword`, `Seed:AdminEmail`), `MustChangePassword = true`.
* Kalender-Abos (ICS) verwenden geheime Tokens in der URL (nur Hash in DB), da Kalender-Clients keine Cookies können (ADR-0005). * Kalender-Abos (ICS) verwenden geheime Tokens in der URL (nur Hash in DB), da Kalender-Clients keine Cookies können (ADR-0005).
## 6. API-Übersicht (Ziel) ## 6. API-Übersicht (Ziel, ✅ = umgesetzt)
| Bereich | Endpunkte | | Bereich | Endpunkte |
|---|---| |---|---|
| Auth | `POST /api/auth/login`, `POST /api/auth/logout`, `GET /api/auth/me`, `POST /api/auth/change-password` | | Auth ✅ | `POST /api/auth/login`, `POST /api/auth/pin-login` (Feature-Flag), `POST /api/auth/logout`, `GET /api/auth/me`, `POST /api/auth/change-password` |
| Admin – Benutzer | `GET/POST /api/admin/users`, `PUT/DELETE /api/admin/users/{id}`, `POST …/{id}/reset-password`, `POST …/{id}/unlock` | | Admin – Benutzer ✅ | `GET/POST /api/admin/users`, `GET/PUT/DELETE /api/admin/users/{id}`, `POST …/{id}/reset-password`, `POST …/{id}/unlock`, `PUT …/{id}/pin`, `PUT/DELETE …/{id}/avatar` |
| Admin – Einstellungen | `GET/PUT /api/admin/settings`, `GET /api/admin/audit` | | Admin – Einstellungen ✅ | `GET /api/settings` (alle), `PUT /api/admin/settings`, `GET /api/admin/audit?page=&pageSize=` |
| Benutzerverzeichnis ✅ | `GET /api/users`, `GET /api/users/{id}/avatar` |
| Einkauf | `GET/POST /api/shopping/lists`, `GET/POST /api/shopping/lists/{id}/items`, `PATCH/DELETE /api/shopping/items/{id}`, `POST /api/shopping/lists/{id}/clear-done`, `GET /api/shopping/suggestions?q=` | | Einkauf | `GET/POST /api/shopping/lists`, `GET/POST /api/shopping/lists/{id}/items`, `PATCH/DELETE /api/shopping/items/{id}`, `POST /api/shopping/lists/{id}/clear-done`, `GET /api/shopping/suggestions?q=` |
| Essen | `GET /api/meals?from=&to=`, `PUT /api/meals/{date}/{mealType}`, `POST /api/meals/{id}/move`, `POST /api/meals/{id}/to-shopping`, `GET/POST /api/meals/wishes` | | Essen | `GET /api/meals?from=&to=`, `PUT /api/meals/{date}/{mealType}`, `POST /api/meals/{id}/move`, `POST /api/meals/{id}/to-shopping`, `GET/POST /api/meals/wishes` |
| Aufgaben | `GET/POST /api/tasks`, `PUT/DELETE /api/tasks/{id}`, `POST /api/tasks/{id}/complete`, `POST /api/tasks/{id}/approve`, `GET /api/tasks/points` | | Aufgaben | `GET/POST /api/tasks`, `PUT/DELETE /api/tasks/{id}`, `POST /api/tasks/{id}/complete`, `POST /api/tasks/{id}/approve`, `GET /api/tasks/points` |
+17 -7
View File
@@ -20,13 +20,20 @@ Legende: `[x]` erledigt, `[ ]` offen. Nach jeder Phase: Build ohne Warnungen, Te
- [x] CI-Pipeline (GitHub Actions) - [x] CI-Pipeline (GitHub Actions)
## Phase 2 – Rollen & Admin ## Phase 2 – Rollen & Admin
- [ ] Policies vollständig (siehe ARCHITECTURE §4) inkl. Unit-Tests je Policy - [x] Rollenmatrix vollständig als `PermissionMatrix` (Full/Restricted/None) + Permission-Policies, Unit-Test je Matrixzelle (ADR-0008)
- [ ] Benutzerverwaltung: anlegen, bearbeiten, deaktivieren, löschen, Rolle, Farbe, Avatar - [x] Konfigurierbare Ausnahmen: Gäste dürfen Einkaufsliste bearbeiten / Familienchat nutzen (wirken sofort auf Policies und `/me`)
- [ ] Passwort-Reset durch Admin, Konto entsperren, Gast-Ablaufdatum - [x] Fallback-Policy prüft zusätzlich das Ablaufdatum (Claim) – abgelaufene Gäste verlieren sofort den Zugriff
- [ ] Schutz „letzter Admin“ - [x] Benutzerverwaltung (Api + UI): anlegen, bearbeiten, deaktivieren, löschen (Soft Delete mit Anonymisierung), Rolle, Anzeigename, Farbe
- [ ] Allgemeine Einstellungen (Familienname, Zeitzone, Essenplan-Zeitraum, Mahlzeiten-Typen, Aufgaben-Kategorien, Punktesystem, Eltern-Bestätigung, Gast-Rechte) - [x] Profilbild-Upload (PNG/JPEG/WebP ≤ 1 MB, Signaturprüfung, in der DB, ADR-0009)
- [ ] Audit-Log für administrative Aktionen + Ansicht - [x] Passwort-Reset durch Admin (erzwungene Änderung), Konto entsperren, Gast-Ablaufdatum (Login danach verweigert)
- [ ] Vorbereitung PIN-Login für Kinder (Datenmodell + Endpoint hinter Feature-Flag) - [x] Schutz „letzter aktiver Admin“ (löschen/deaktivieren/herabstufen/ablaufen → 409)
- [x] Sitzungen werden bei Rollen-/Status-Änderung über den Security Stamp ungültig (≤ 5 min)
- [x] Allgemeine Einstellungen als Entität + Admin-Seite (Familienname, Zeitzone, Essenplan-Zeitraum, Mahlzeiten, Aufgaben-Kategorien, Punktesystem, Eltern-Bestätigung, Gast-Rechte)
- [x] Audit-Log für alle administrativen Aktionen (inkl. Änderungsdetails) + Admin-Ansicht mit Paging
- [x] Benutzerverzeichnis `/api/users` (Namen, Farben, Avatare für spätere Zuweisungen)
- [x] Validierung mit FluentValidation (gemeinsame Validatoren für Api und Client), Migration `Phase2AdminAndSettings`
- [x] PIN-Login für Kinder vorbereitet: PIN setzen/entfernen (nur Kinder), Endpoint hinter `Features:PinLogin` (ADR-0010)
- [x] Tests: Unit (Matrix, Policies, Last-Admin, Validatoren, Avatar-Prüfung), Integration (Admin-Endpunkte, 403 für Nicht-Admins, Gast-Ablauf, Lockout, Audit), bUnit (Benutzerliste, Dialog)
## Phase 3 – Einkaufsliste ## Phase 3 – Einkaufsliste
- [ ] Mehrere Listen, Standardliste - [ ] Mehrere Listen, Standardliste
@@ -93,6 +100,9 @@ Legende: `[x]` erledigt, `[ ]` offen. Nach jeder Phase: Build ohne Warnungen, Te
- [ ] README: Voraussetzungen, Entwicklung, Konfiguration, Deployment, Update, Backup/Restore, Kalender-Abos - [ ] README: Voraussetzungen, Entwicklung, Konfiguration, Deployment, Update, Backup/Restore, Kalender-Abos
## Backlog ## Backlog
- PIN-Login-Oberfläche für ein Familien-Tablet (Profilauswahl + Ziffernblock) – Api ist vorbereitet
- Selbst-Upload des eigenen Profilbilds, Profilseite für Anzeigename/Farbe
- Endgültige Löschung eines Benutzers (DSGVO) zusätzlich zum Soft Delete
- Belohnungskatalog für Ämtchen-Punkte - Belohnungskatalog für Ämtchen-Punkte
- Web-Push für Termine und Chat - Web-Push für Termine und Chat
- CalDAV-Server bzw. Microsoft-Graph-Synchronisation - CalDAV-Server bzw. Microsoft-Graph-Synchronisation
+26
View File
@@ -0,0 +1,26 @@
# ADR-0008: Berechtigungen als Rollenmatrix mit Zugriffsstufen
* Status: akzeptiert
* Datum: 2026-10-09
## Kontext
Die Anforderungen enthalten eine Matrix aus Funktion × Rolle mit Abstufungen wie „nur eigene“, „nur zugewiesene“,
„nur Wünsche vorschlagen“ oder „nur wenn freigegeben“. Einige Ausnahmen sind konfigurierbar (Gäste dürfen Einkaufsliste bearbeiten,
Gäste im Familienchat). Rein rollenbasierte Policies (`RequireRole`) bilden das nicht ab und würden sich über Slices verstreuen.
## Entscheidung
* `FamilyPlanner.Domain.Authorization.PermissionMatrix` ist die **einzige Quelle** der Matrix: je `Permission` und Rolle eine
`AccessLevel` (`None`, `Restricted`, `Full`). Konfigurierbare Ausnahmen kommen als `PermissionOptions` aus den Familieneinstellungen.
* Die Api registriert je `Permission` eine Policy `Permission:<Name>` (`Policies.For(permission)`). Ein `PermissionHandler`
wertet die Matrix mit den aktuellen Einstellungen aus (gecacht über `ISettingsProvider`, beim Speichern invalidiert).
Endpunkte nutzen `.RequirePermission(Permission.X)`.
* `Restricted` heißt: Der Endpunkt ist erreichbar, der Slice **muss** objektbezogen prüfen (z. B. Kind löscht nur eigene Einträge).
Diese Prüfungen entstehen mit den jeweiligen Fach-Slices (Phasen 3–8) und werden dort getestet.
* Jede Permission-Policy schließt die Fallback-Bedingungen ein (angemeldet, nicht abgelaufen, Passwortwechsel erledigt).
* Der Client erhält die **wirksamen** Berechtigungen über `GET /api/auth/me` (`CurrentUserDto.Permissions`) und nutzt sie nur zum
Ein-/Ausblenden (Claims `fp:permission`, dieselben Policy-Namen).
* Die rollenbasierten Policies `Admin`, `Parent`, `FamilyMember`, `AnyRole` bleiben für grobe Bereiche (z. B. Seite `/admin`).
## Konsequenzen
* Eine Änderung der Matrix ist eine Zeile im Domain-Projekt; Tests (`PermissionMatrixTests`, `ApiPolicyTests`) prüfen jede Zelle.
* Änderungen an den Gast-Ausnahmen wirken sofort (Cache-Invalidierung im selben Prozess; bei mehreren Api-Instanzen nach max. 1 Minute).
@@ -0,0 +1,19 @@
# ADR-0009: Profilbilder in der Datenbank
* Status: akzeptiert
* Datum: 2026-10-09
## Kontext
Benutzer erhalten ein optionales Profilbild. Die Datenmenge ist klein (wenige Familienmitglieder), Backups sollen einfach bleiben
(ein SQL-Backup, siehe `deploy/scripts/backup.sh`), und es soll kein zusätzliches Volume nötig sein.
## Entscheidung
* Bilder liegen in der Tabelle `UserAvatars` (1:1 zum Benutzer, `varbinary(max)`), höchstens **1 MB**.
* Erlaubt sind PNG, JPEG und WebP. Der Typ wird serverseitig anhand der **Datei-Signatur** (Magic Bytes) bestimmt – Dateiname und
Content-Type des Clients werden ignoriert; SVG ist bewusst ausgeschlossen (Skript-Risiko).
* Auslieferung über `GET /api/users/{id}/avatar` (nur für angemeldete Benutzer). Die URL enthält eine Version (`?v=…`), daher
`Cache-Control: private, max-age=604800`.
* Hochladen/Entfernen nur über die Admin-Api (`PUT/DELETE /api/admin/users/{id}/avatar`). Ein Selbst-Upload durch Benutzer kann später ergänzt werden.
## Konsequenzen
Keine Dateisystem-Abhängigkeit, Bilder sind Teil des Datenbank-Backups. Für große Bildmengen (z. B. Chat-Anhänge, Phase 8) wird separat entschieden.
+38
View File
@@ -0,0 +1,38 @@
# ADR-0010: Benutzerlebenszyklus – Deaktivieren, Ablauf, Löschen, PIN-Login
* Status: akzeptiert
* Datum: 2026-10-09
## Entscheidung
**Anmelden verweigern:** `AppSignInManager.CanSignInAsync` lehnt deaktivierte, gelöschte und abgelaufene Konten ab
(`AppUser.CanSignIn`). Der Login-Endpunkt meldet „deaktiviert oder abgelaufen“ (403) nur bei korrektem Passwort, sonst die
allgemeine Fehlermeldung (401) – der Kontostatus wird nicht ohne Passwort preisgegeben.
**Laufende Sitzungen:** Bei sicherheitsrelevanten Änderungen (Rolle, aktiv/inaktiv, Ablaufdatum, Löschen, Passwort-Reset) wird der
Security Stamp erneuert; das Cookie wird spätestens nach 5 Minuten ungültig. Zusätzlich steht das Ablaufdatum eines Gastzugangs als
Claim `fp:expires_at` im Cookie und wird von der Fallback-Policy bei **jedem** Request geprüft – ein Gast verliert den Zugriff also
exakt zum Ablaufzeitpunkt, ohne Datenbankabfrage.
**Löschen = Soft Delete mit Anonymisierung:** `AppUser` implementiert `ISoftDeletable` (globaler Query-Filter, auch für Identity).
Beim Löschen bleiben Id und Anzeigename (für Verlauf, Audit, spätere Zuordnungen), Benutzername wird zu `geloescht-<id>`
(der Name ist danach wieder frei), E-Mail, Passwort-Hash, PIN und Profilbild werden entfernt.
**Letzter Admin:** `AdminGuard.WouldRemoveLastActiveAdmin` verhindert, dass der letzte aktive (nicht abgelaufene, nicht gelöschte)
Administrator gelöscht, deaktiviert, mit Ablaufdatum versehen oder herabgestuft wird (409 Conflict).
**Eine Rolle je Benutzer:** Die Admin-Oberfläche vergibt genau eine der vier Rollen; ein Rollenwechsel ersetzt die bisherige.
**Passwort-Reset durch Admin:** Neues vorläufiges Passwort (über Reset-Token, damit Passwortregeln greifen), `MustChangePassword = true`,
Kontosperre wird aufgehoben.
**PIN-Login (vorbereitet):** Admins können für Kinder eine 4–6-stellige PIN setzen (gehasht wie Passwörter). `POST /api/auth/pin-login`
existiert, ist aber nur aktiv, wenn `Features:PinLogin=true` gesetzt ist (Standard: aus → 404). Fehlversuche zählen zur normalen
Kontosperre. Die Tablet-Oberfläche (Profilauswahl + Ziffernblock) folgt mit dem Wand-Tablet-Modus (Backlog).
**Audit-Log:** Alle administrativen Aktionen schreiben einen Eintrag (`AuditLog`-Tabelle) mit Anzeigename des Handelnden, Aktion,
Ziel und Änderungsdetails (alt → neu, keine Passwörter). Ansicht mit Paging im Adminbereich.
## Konsequenzen
Gelöschte Benutzer bleiben als Datensatz erhalten (Verweise in Aufgaben/Terminen/Chat bleiben gültig). Eine endgültige Löschung
(DSGVO-Auskunft/Löschung) kann später als eigener Admin-Vorgang ergänzt werden.