# CONFIGURATION.md — omsorgCore

Übersicht aller Konfigurationswerte des Backends: was ist einstellbar, was ist der Default, wie überschreibt man ihn. Ziel: **kein Wert, den ein Betreiber sinnvoll ändern könnte, steckt fest im Code.**

## Wie Konfiguration geladen wird

ASP.NET Core liest Konfiguration in dieser Reihenfolge (später gewinnt):

1. `appsettings.json` — Defaults, die für jede Umgebung gelten.
2. `appsettings.{ASPNETCORE_ENVIRONMENT}.json` (z. B. `appsettings.Development.json`) — Overrides pro Umgebung. **Enthält keine echten Secrets**, nur `CHANGE_ME_...`-Platzhalter.
3. Umgebungsvariablen — verschachtelte Keys wie `Jwt:Secret` werden zu `Jwt__Secret` (doppelter Unterstrich statt Doppelpunkt).
4. In Development zusätzlich: `dotnet user-secrets` (lokal, nicht eingecheckt) — `dotnet user-secrets init` im `OmsorgCore.Api`-Projekt einmalig ausführen, dann z. B. `dotnet user-secrets set "ConnectionStrings:OmsorgCore" "..."`.

**Secrets nie einchecken.** `Jwt:Secret`, `ConnectionStrings:OmsorgCore` und (sobald produktiv genutzt) `Seed:AdminPassword` haben in den eingecheckten `appsettings*.json`-Dateien nur Platzhalter — echte Werte kommen aus Umgebungsvariablen oder User-Secrets.

## Auth / JWT

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `Jwt:Secret` | *(kein Default, Platzhalter in Dev)* | `Jwt__Secret` | Signaturschlüssel für Access-Tokens. Muss mind. 32 Zeichen, produktiv nur per Env-Var/Secret-Store. |
| `Jwt:Issuer` | `"OmsorgCore"` | `Jwt__Issuer` | JWT-`iss`-Claim, wird beim Validieren geprüft. |
| `Jwt:Audience` | `"OmsorgClients"` | `Jwt__Audience` | JWT-`aud`-Claim, wird beim Validieren geprüft. |
| `Jwt:ExpiryMinutes` | `60` | `Jwt__ExpiryMinutes` | Gültigkeitsdauer eines Access-Tokens. |
| `RefreshToken:ExpiryDays` | `60` | `RefreshToken__ExpiryDays` | Gültigkeitsdauer eines Refresh-Tokens (sliding — jede Nutzung/Rotation verlängert effektiv die Session). |
| `Auth:MaxLoginFailures` | `5` | `Auth__MaxLoginFailures` | Fehlversuche pro IP innerhalb von `Auth:LoginLockoutMinutes`, ab denen `POST /api/auth/login` mit `429` sperrt (zentral für alle Clients, siehe `AuthService.LoginAsync`/`LoginAttempt`). |
| `Auth:LoginLockoutMinutes` | `10` | `Auth__LoginLockoutMinutes` | Zeitfenster, in dem Fehlversuche gezählt werden, bevor die Sperre wieder abläuft. |
| `PasswordPolicy:MinLength` | `8` | `PasswordPolicy__MinLength` | Mindestlänge für jedes neu gesetzte Passwort (Account-Anlage, Admin-Reset, Passwort ändern, Passwort-vergessen-Reset — zentral über `IPasswordPolicy`, siehe `omsorgCore/CLAUDE.md`). Über `GET /api/auth/password-policy` auch unauthentifiziert abrufbar, damit Clients denselben Wert für Hinweistexte/Vorab-Validierung nutzen können. |

## Dokumentenarchiv / Storage

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `Storage:DocumentsRootPath` | `"App_Data/documents"` | `Storage__DocumentsRootPath` | Wurzelverzeichnis, unter dem Dokument-Uploads (FR-MA-3) physisch auf dem Dateisystem abgelegt werden — relativ zum Arbeitsverzeichnis der `OmsorgCore.Api`, wenn kein absoluter Pfad angegeben wird. Nur der Pfad/Metadaten landen in Postgres, nicht die Bytes selbst (siehe `omsorgCore/CLAUDE.md`, "Dokumentenarchiv"). Produktiv auf einen echten, persistenten Pfad zeigen (nicht das Deployment-Verzeichnis selbst). |
| `Storage:MaxDocumentSizeBytes` | `20971520` (20 MB) | `Storage__MaxDocumentSizeBytes` | Maximal erlaubte Dateigröße pro Upload, geprüft in `IDocumentUploadPolicy`/`DocumentService.UploadAsync`. |
| `Storage:AllowedDocumentContentTypes` | `"application/pdf,image/jpeg,image/png"` | `Storage__AllowedDocumentContentTypes` | Kommagetrennte Liste erlaubter `Content-Type`-Werte für Uploads. |

## Passwort-Reset / E-Mail

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `Email:Provider` | `"Console"` | `Email__Provider` | Mail-Versandweg. `Console` schreibt Mails nur ins Log (sicherer Default). `Smtp` versendet echt über die Werte unten (implementiert über MailKit). |
| `Email:PinExpiryMinutes` | `5` | `Email__PinExpiryMinutes` | Gültigkeitsdauer des 6-stelligen PINs beim "Passwort vergessen"-Flow. |
| `Email:MaxAttempts` | `3` | `Email__MaxAttempts` | Maximale Anzahl Fehlversuche bei der PIN-Eingabe, bevor der Code invalidiert wird. |
| `Email:ResetTokenExpiryMinutes` | `10` | `Email__ResetTokenExpiryMinutes` | Gültigkeitsdauer des Reset-Tokens nach erfolgreicher PIN-Verifikation. |
| `Email:RequestCooldownSeconds` | `60` | `Email__RequestCooldownSeconds` | Mindestabstand zwischen zwei Reset-Anfragen desselben Users (Spam-Schutz). |

**Ein SMTP-Server, mehrere Accounts:** die Verbindungsdaten (`Email:Smtp:*`) sind einmal gemeinsam konfiguriert, Login + Absenderidentität liegen pro benanntem Account unter `Email:Accounts:<Name>:*` — so lässt sich z. B. ein Account exklusiv für Passwort-Reset-Mails führen, getrennt von einem künftigen allgemeinen Absender, ohne einen zweiten Server zu brauchen.

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `Email:Smtp:Host` | *(leer)* | `Email__Smtp__Host` | SMTP-Server-Adresse. Nur wirksam, wenn `Email:Provider` = `"Smtp"`. |
| `Email:Smtp:Port` | `587` | `Email__Smtp__Port` | SMTP-Port. |
| `Email:Smtp:EnableSsl` | `true` | `Email__Smtp__EnableSsl` | Ob StartTLS/SSL beim Verbindungsaufbau verwendet wird. |
| `Email:Accounts:<Name>:Username` | *(leer, Platzhalter in Dev)* | `Email__Accounts__<Name>__Username` | SMTP-Login-Username dieses Accounts. Secret — nie echten Wert einchecken. |
| `Email:Accounts:<Name>:Password` | *(leer, Platzhalter in Dev)* | `Email__Accounts__<Name>__Password` | SMTP-Login-Passwort dieses Accounts. Secret — produktiv nur per Env-Var/user-secrets, **nie** in `appsettings*.json` einchecken. |
| `Email:Accounts:<Name>:SenderAddress` | *(leer)* | `Email__Accounts__<Name>__SenderAddress` | Absenderadresse dieses Accounts. |
| `Email:Accounts:<Name>:SenderDisplayName` | `"OMSORG"` | `Email__Accounts__<Name>__SenderDisplayName` | Absendername dieses Accounts. |
| `Email:PasswordResetAccount` | `"PasswordReset"` | `Email__PasswordResetAccount` | Welcher Account-Name aus `Email:Accounts` für Passwort-Reset-Mails verwendet wird. Standardmäßig ist `"PasswordReset"` bereits als Account in `appsettings.json` angelegt (leer, muss per Env-Var/user-secrets befüllt werden). |
| `Email:PasswordResetTemplate:Subject` | `"Ihr OMSORG Passwort-Reset-Code"` | `Email__PasswordResetTemplate__Subject` | Betreff der Passwort-Reset-Mail. |
| `Email:PasswordResetTemplate:BodyTemplate` | siehe `appsettings.json` | `Email__PasswordResetTemplate__BodyTemplate` | Text der Passwort-Reset-Mail. Platzhalter im Format `$NAME$`: `$RESET_PIN$` (der 6-stellige Code) und `$RESET_PIN_EXPIRY_MINUTES$` (aus `Email:PinExpiryMinutes`). Ersetzung über `OmsorgCore.Email.EmailTemplateRenderer`. |
| `Email:UserInviteAccount` | `"UserInvite"` | `Email__UserInviteAccount` | Welcher Account-Name aus `Email:Accounts` für Account-Einladungsmails (neuer User-Account für einen Mitarbeiter) verwendet wird. |
| `Email:UserInviteTemplate:Subject` | `"Ihr OMSORG-Zugang"` | `Email__UserInviteTemplate__Subject` | Betreff der Einladungsmail. |
| `Email:UserInviteTemplate:BodyTemplate` | siehe `appsettings.json` | `Email__UserInviteTemplate__BodyTemplate` | Text der Einladungsmail. Platzhalter: `$INVITE_PIN$`, `$INVITE_PIN_EXPIRY_DAYS$`. Die Gültigkeitsdauer wird beim Anlegen des Accounts pro Einladung gewählt (`POST /api/users`, `PinValidityDays`, Default 7), nicht global konfiguriert — der Einladungs-PIN läuft über dieselbe `PasswordResetCode`-Tabelle wie der Passwort-vergessen-Flow, nur mit individuell längerer Gültigkeit statt der 5 Minuten aus `Email:PinExpiryMinutes`. |

**Kein API-Endpoint zum Setzen dieser Werte:** SMTP-Zugangsdaten und Mail-Texte werden bewusst nie über eine vom Client erreichbare Route entgegengenommen oder ausgeliefert — nur über die üblichen serverseitigen Konfigurationswege oben. Ein `POST /api/admin/email/test-send` (siehe `AdminEmailController`) löst lediglich den Versand einer festen Testmail über die bereits konfigurierte Verbindung aus, liefert aber nie Host/Username/Passwort an den Client zurück.

## Seed (nur Development)

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `Seed:AdminUsername` | `"admin"` | `Seed__AdminUsername` | Username des automatisch angelegten Standard-Admin-Users. |
| `Seed:AdminPassword` | `"abersicher"` | `Seed__AdminPassword` | Passwort des Standard-Admin-Users. |

Der Seed läuft nur, wenn `ASPNETCORE_ENVIRONMENT=Development` **und** noch kein `User` in der DB existiert (idempotent, siehe `src/OmsorgCore.Infrastructure/Persistence/DbSeeder.cs`). In Produktion läuft er nicht — dort braucht es einen echten User-Anlage-Flow (noch nicht gebaut, siehe `omsorgCore/CLAUDE.md`, "Offene Punkte").

## Datenbank

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `ConnectionStrings:OmsorgCore` | *(kein Default, Platzhalter in Dev)* | `ConnectionStrings__OmsorgCore` | Postgres-Connection-String (`Host=...;Port=5432;Database=omsorg_core;Username=...;Password=...`). |

## Logging / Hosting

| Key | Default | Env-Var | Beschreibung |
|---|---|---|---|
| `Logging:LogLevel:Default` | `"Information"` | `Logging__LogLevel__Default` | Standard-Log-Level. |
| `Logging:LogLevel:Microsoft.AspNetCore` | `"Warning"` | `Logging__LogLevel__Microsoft.AspNetCore` | Log-Level für das ASP.NET-Core-Framework selbst. |
| `AllowedHosts` | `"*"` | `AllowedHosts` | Host-Header-Filter von ASP.NET Core. |

## Bewusst nicht konfigurierbar

Diese Werte sind absichtlich fest im Code und **keine** Konfigurationslücke:

- **`ModuleType`, `PermissionAction`, `PermissionEffect`** (`src/OmsorgCore.Domain/Enums/`) — die Rechtematrix-Bausteine. Compile-Time-Domänenkonzepte: ein neues Modul oder eine neue Aktion braucht ohnehin neuen Controller-Code, ein Config-Wert würde hier nur Scheinflexibilität vortäuschen.
- **JWT-Validierungs-Invarianten** in `Program.cs` (`ValidateIssuer/Audience/Lifetime/IssuerSigningKey = true`) — Sicherheits-Grundannahmen, kein Betriebsparameter.
- **Passwort-Hashing-Algorithmus** (`PasswordHasher<T>`, ASP.NET-Identity-Standard-PBKDF2-Iterationszahl) — bewusst der Framework-Default, kein eigenes Krypto-Rad.