- omsorgapp: drop Electron, run as a plain Vite/React browser app; refresh token moves to an HttpOnly cookie (omsorgCore), CORS added for the new browser origin, document download/preview switched to Blob-based browser APIs. - Add Dockerfiles for omsorgCore, omsorgapp, and omsorgWeb, a docker-compose.yml wiring Postgres/MySQL/all three apps together, and a Gitea Actions workflow that builds and pushes images to the repo's container registry on push to main and on version tags. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
10 KiB
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):
appsettings.json— Defaults, die für jede Umgebung gelten.appsettings.{ASPNETCORE_ENVIRONMENT}.json(z. B.appsettings.Development.json) — Overrides pro Umgebung. Enthält keine echten Secrets, nurCHANGE_ME_...-Platzhalter.- Umgebungsvariablen — verschachtelte Keys wie
Jwt:Secretwerden zuJwt__Secret(doppelter Unterstrich statt Doppelpunkt). - In Development zusätzlich:
dotnet user-secrets(lokal, nicht eingecheckt) —dotnet user-secrets initimOmsorgCore.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.