instances added god may help

This commit is contained in:
2026-02-23 12:58:19 +01:00
parent 25370a1a55
commit 99db252f60
290 changed files with 9858 additions and 4914 deletions

View File

@@ -1,6 +1,6 @@
# Architektur
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22 (geprüft)
## 1) Zielbild
@@ -32,16 +32,18 @@ Ablauf pro Request:
## 3) Routing-Modell
- Route-Definitionen liegen in `/config/routes.php`.
- Registrierung erfolgt in `/config/router.php`.
- `public=true` in `routes.php` steuert, welche Pfade ohne Login erlaubt sind.
- Route-Definitionen (Pfad-Aliase) liegen in `/config/routes.php`.
- Registrierung erfolgt in `/config/router.php` (liest nur `path` und `target`; das `public`-Feld wird vom Router nicht ausgewertet).
- Welche Pfade ohne Login erreichbar sind, steuert `lib/Http/AccessControl.php`:
- Hartkodierte `ALWAYS_PUBLIC_PREFIXES` (z. B. `auth/microsoft/`, `api/`, `branding/`, `flash/`)
- `APP_PUBLIC_PATHS`-Konstante (gesetzt in `config/config.php`)
MintyPHP-Konvention (Dateinamensrouting):
- `pages/admin/users/edit($id).php` erwartet URL-Parameter `id`
- `pages/admin/users/data().php` für Datenendpunkte
- `...(none).phtml` für responses ohne Template-Layout
- Binarausgaben (z. B. Onboarding-PDF/ZIP) setzen `MINTY_ALLOW_OUTPUT` im Action-Endpoint.
- Binärausgaben (z. B. Onboarding-PDF/ZIP) setzen `MINTY_ALLOW_OUTPUT` im Action-Endpoint.
## 4) Schichten und Verantwortungen
@@ -57,6 +59,15 @@ MintyPHP-Konvention (Dateinamensrouting):
- Geschäftsregeln
- Cross-Cutting-Logik (z. B. Auth, Settings, Branding)
- Orchestrierung mehrerer Repositories
- Scheduler/Lifecycle sind instanzbasiert über `lib/Service/Scheduler/SchedulerServicesFactory.php` verdrahtet.
- Import/Import-Audit sind instanzbasiert über `lib/Service/Import/ImportServicesFactory.php` verdrahtet.
- User-Domain ist instanzbasiert über `lib/Service/User/UserServicesFactory.php` verdrahtet
(Services: `UserAccountService`, `UserAssignmentService`, `UserTenantContextService`, `UserPasswordService`;
Repositories: `UserReadRepository`, `UserWriteRepository`, `UserListQueryRepository` + Assignment-Repositories).
- Auth-/SSO-Domain ist instanzbasiert über `lib/Service/Auth/AuthServicesFactory.php` verdrahtet
(u. a. `AuthService`, `RememberMeService`, `SsoUserLinkService`, `TenantSsoService`, `MicrosoftOidcService`).
- Auth-Infra-Repositories (`ApiTokenRepository`, `RememberTokenRepository`, `PasswordResetRepository`,
`EmailVerificationRepository`) werden ebenfalls instanzbasiert über die Factory injiziert.
### Repository-Layer (`/lib/Repository`)
@@ -180,8 +191,8 @@ So bleibt die Trennung stabil und Refactoring kontrollierbar.
- `settings` (DB) ist die alleinige fachliche Quelle für globale Settings.
- `config/settings.php` ist ein technischer Teil-Cache für sehr häufig gelesene UI-Basiswerte.
- Zugriffsmuster:
- `SettingService::get*` / `SettingService::set*` -> DB
- `appSetting(...)` -> Datei-Cache (`SettingCacheService`)
- `SettingGateway` (verdrahtet über `SettingServicesFactory`) -> `SettingService` -> DB
- `appSetting(...)` -> `SettingServicesFactory` -> `SettingCacheService` (Datei-Cache)
- Konsequenz:
- Es ist korrekt, dass nicht alle DB-Settings im Datei-Cache stehen (z. B. SMTP/SSO/API-Details).
- Der Cache muss nur die Keys enthalten, die im Runtime-Helper `appSetting(...)` verwendet werden.

View File

@@ -0,0 +1,78 @@
# Auth/SSO Smoke-Test
Letzte Aktualisierung: 2026-02-22
Dieser Runbook prüft die 5 kritischen Flows nach Auth/SSO-Änderungen:
- Login (Passwort)
- Microsoft Start
- Microsoft Callback
- Tenant-SSO speichern
- Logout
## Voraussetzungen
- Test-User ist aktiv und hat mindestens einen aktiven Tenant.
- Es gibt einen Tenant mit Microsoft-SSO-Konfiguration.
- Für Microsoft-Flow: gültige Tenant-SSO-Credentials vorhanden (Shared oder Override).
- Testumgebung läuft und du bist als Admin eingeloggt.
## 1) Login (Passwort)
1. `login` aufrufen (optional `login?tenant=<slug>`).
2. Gültige E-Mail + Passwort eingeben.
3. Erwartung:
- Login erfolgreich, Redirect auf `admin`.
- Session enthält `user`, `available_tenants`, `current_tenant`.
- Kein Fehler-Flash.
Negativtest:
- Falsches Passwort -> generischer Fehler (kein Account-Leak), kein Login.
## 2) Microsoft Start (`auth/microsoft/start`)
1. `auth/microsoft/start?tenant=<slug>` aufrufen.
2. Erwartung:
- Bei gültigem Tenant + SSO-Config: Redirect zur Microsoft-Authorize-URL.
- URL enthält `state`, `nonce`, PKCE (`code_challenge`).
3. Negativtest:
- Ungültiger Tenant-Slug -> Redirect auf `login` mit passendem Flash.
- SSO deaktiviert/inkomplett -> Redirect zurück auf Tenant-Login mit passendem Flash.
## 3) Microsoft Callback (`auth/microsoft/callback`)
1. Erfolgreichen Callback durchlaufen lassen.
2. Erwartung:
- `state` wird einmalig konsumiert (kein Replay).
- ID-Token-Prüfungen greifen (`aud`, `iss`, `tid`, `exp/nbf`, `nonce`, Signatur).
- User wird verknüpft/provisioniert und auf `admin` weitergeleitet.
Negativtests:
- Callback mit altem/wiederverwendetem `state` -> Login schlägt sauber fehl.
- Callback ohne `code`/mit Fehler -> Redirect `login` mit Fehler-Flash.
## 4) Tenant-SSO speichern (Admin)
1. `admin/tenants/edit/<uuid>` öffnen, Tab `SSO`.
2. Pflichtwerte setzen:
- `Microsoft login` aktiv
- `Entra tenant ID`
- Shared App oder Override-Credentials vollständig
3. Speichern.
4. Erwartung:
- Save erfolgreich.
- UI-Status „Konfiguration vollständig“.
- Bei `enforce_microsoft_login=1` und unvollständiger Config: Save wird blockiert.
## 5) Logout
1. Als eingeloggter User `logout` aufrufen.
2. Erwartung:
- Session beendet.
- Remember-Me Token wird vergessen.
- Redirect auf `login`.
## Schnellcheck (optional)
- In Browser A einloggen, dann in Browser B denselben User ändern (z. B. deaktivieren).
- In Browser A nächste Anfrage auslösen.
- Erwartung: bestehende Session-Refresh-Logik greift (ggf. Logout bei inaktiv/ohne Tenant).

View File

@@ -1,6 +1,6 @@
# Benutzer-Lifecycle-Policy
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22
## Ziel
@@ -45,6 +45,7 @@ Automatische Lifecycle-Aktionen ignorieren Benutzer mit mindestens einer aktiven
- Endpoint: `POST /admin/settings/run-user-lifecycle`
- Schutz: Login + `settings.update` + CSRF
- Verdrahtung: `SchedulerServicesFactory` erstellt `UserLifecycleService` instanzbasiert.
### Cron/CLI

View File

@@ -1,6 +1,6 @@
# Einstellungen: Speicherung (DB + Cache)
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22
## Kurzfassung
@@ -41,8 +41,8 @@ Daher ist es korrekt, wenn diese Werte in `settings` (DB) vorhanden sind, aber n
## Laufzeitfluss
1. Admin speichert Settings in `admin/settings`.
2. `SettingService::set*` schreibt in DB.
3. `SettingCacheService::update(...)` aktualisiert den Datei-Cache nur für den definierten Teilbereich.
2. `SettingGateway` (aus `SettingServicesFactory`) delegiert auf `SettingService`, der in DB schreibt.
3. `SettingCacheService` aktualisiert den Datei-Cache nur für den definierten Teilbereich.
4. UI-Helfer `appSetting(...)` liest danach aus Datei-Cache.
## Wenn DB und Datei scheinbar nicht zusammenpassen

View File

@@ -1,6 +1,6 @@
# Leitfaden für die erste Änderung
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22
## Ziel
@@ -11,7 +11,10 @@ Ein neuer Entwickler soll den ersten Change strukturiert und reproduzierbar umse
### Schrittfolge
1. Data-Endpoint erweitern (z. B. `pages/admin/users/data().php`)
2. Falls nötig: neue Repository-Query/Count-Methode (z. B. `lib/Repository/User/UserRepository.php`)
2. Falls nötig: neue Repository-Methode ergänzen je nach Typ:
- Listenabfrage / Filterung / Paginierung → `lib/Repository/User/UserListQueryRepository.php`
- Einzelnes Objekt lesen → `lib/Repository/User/UserReadRepository.php`
- Schreiboperation → `lib/Repository/User/UserWriteRepository.php`
3. Grid-Spalte in `pages/admin/<modul>/index(default).phtml` ergänzen (z. B. `pages/admin/users/index(default).phtml`)
4. i18n-Key für Spaltennamen setzen
5. `php -l` + manueller UI-Test

View File

@@ -84,7 +84,7 @@ Unterstützte Typen:
### Architektur-überblick
Jeder Job ist eine eigene Handler-Klasse, die `ScheduledJobHandlerInterface` implementiert.
Die Registry ist eine reine Map von `job_key` zu Handler-Klasse sie enthält keine
Die Registry ist eine Map von `job_key` zu Handler-Instanz sie enthält keine
job-spezifische Logik.
```
@@ -93,6 +93,7 @@ lib/Service/Scheduler/
ScheduledJobHandlerInterface.php ← Vertrag für alle Handler
UserLifecycleJobHandler.php ← Referenz-Implementierung
ScheduledJobRegistry.php ← Handler-Map (einzige Datei die bei neuen Jobs angefasst wird)
SchedulerServicesFactory.php ← verdrahtet Abhängigkeiten zentral
```
### Schritt-für-Schritt
@@ -110,7 +111,11 @@ use MintyPHP\Service\MeinBereich\MeinService;
class MeinJobHandler implements ScheduledJobHandlerInterface
{
public static function definition(): array
public function __construct(private readonly MeinService $meinService)
{
}
public function definition(): array
{
return [
'label' => 'Mein Job',
@@ -126,9 +131,9 @@ class MeinJobHandler implements ScheduledJobHandlerInterface
];
}
public static function execute(?int $actorUserId): array
public function execute(?int $actorUserId): array
{
$result = MeinService::run($actorUserId);
$result = $this->meinService->run($actorUserId);
if (!($result['ok'] ?? false)) {
$errorCode = (string) ($result['error'] ?? 'job_failed');
@@ -162,20 +167,22 @@ Datei: `lib/Service/Scheduler/ScheduledJobRegistry.php`
// Konstante hinzufügen:
public const MEIN_JOB = 'mein_job';
// In handlers() eine Zeile hinzufügen:
private static function handlers(): array
{
return [
self::USER_LIFECYCLE_RUN => UserLifecycleJobHandler::class,
self::MEIN_JOB => MeinJobHandler::class, // ← neu
];
}
// In __construct() eine Zeile hinzufügen:
$this->handlers = [
self::USER_LIFECYCLE_RUN => new UserLifecycleJobHandler($userLifecycleService),
self::MEIN_JOB => new MeinJobHandler($meinService), // ← neu
];
```
**3. Fertig.**
**3. Factory-Verdrahtung ergänzen**
Keine anderen Dateien müssen angefasst werden. `ensureSystemJobs()` legt den Job beim
nächsten Scheduler-Aufruf automatisch in der Datenbank an.
Datei: `lib/Service/Scheduler/SchedulerServicesFactory.php`
- `MeinService` instanziieren bzw. bereitstellen
- beim Erzeugen von `ScheduledJobRegistry` den Service mit übergeben
Danach legt `ensureSystemJobs()` den Job beim nächsten Scheduler-Aufruf automatisch
in der Datenbank an.
### Regeln für Handler
@@ -202,7 +209,8 @@ nächsten Scheduler-Aufruf automatisch in der Datenbank an.
`lib/Service/Scheduler/Handler/UserLifecycleJobHandler.php` ist die vollständige
Referenz-Implementierung mit internem Service-Lock, Fehlerbehandlung und
job-spezifischem `result`-Payload.
job-spezifischem `result`-Payload. Der Objektgraph wird über
`lib/Service/Scheduler/SchedulerServicesFactory.php` gebaut.
---
@@ -219,6 +227,6 @@ job-spezifischem `result`-Payload.
- `lock_not_acquired`:
- Ein anderer Runner läuft bereits.
- Job bleibt auf `running`:
- stale-running Schutz erlaubt nach 2 Stunden wieder neue Runs (Schwellwert in `ScheduledJobRepository::markRunning`).
- stale-running Schutz erlaubt nach 2 Stunden wieder neue Runs (Schwellwert in `ScheduledJobRepository->markRunning`).
- Kein fälliger Job:
- `next_run_at`, `enabled`, `timezone` und schedule Konfiguration prüfen.

View File

@@ -1,9 +1,17 @@
# Importe
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22
Diese Seite beschreibt den V1 CSV-Import unter `/admin/imports`.
## Technischer Aufbau
- Das Import-Modul ist instanzbasiert über `lib/Service/Import/ImportServicesFactory.php` verdrahtet.
- `ImportService` orchestriert Analyze/Preview/Commit.
- `ImportStateStoreService` kapselt den tokenbasierten Session-State (`admin_imports_v1`, TTL 2h).
- `CsvReaderService` und `ImportTempFileService` sind eigene Instanzservices.
- Import-Audit läuft über `ImportAuditService` + `ImportAuditRunRepository`.
## Scope (V1)
- Profile: `users`, `departments`.

View File

@@ -1,6 +1,6 @@
# Dokumentation
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22
Diese Dokumente sind für Entwickler gedacht, die die Anwendung warten oder erweitern.
@@ -63,6 +63,8 @@ Diese Dokumente sind für Entwickler gedacht, die die Anwendung warten oder erwe
- Alle ENV-Variablen mit Beschreibung, Standardwerten und Produktions-Checkliste.
- `/docs/fehlerbehebung.md`
- Häufige Fehlerbilder und schnelle Lösungen.
- `/docs/auth-sso-smoke-test.md`
- Manueller Smoke-Runbook für Login, Microsoft-Start/Callback, Tenant-SSO-Save und Logout.
- `/docs/docker-lokal.md`
- Schneller lokaler Docker-Start inkl. typischer Befehle und Troubleshooting.
- `/docs/docker-produktiv.md`

View File

@@ -25,6 +25,8 @@ Diese Regeln sind projektweit verbindlich, um Lesbarkeit und Wartbarkeit stabil
- Keine HTML-Ausgabe.
- Keine direkten View-Annahmen.
- Bei globalen Settings bleibt DB die Quelle (`SettingService`); Datei-Cache nur gezielt für `appSetting(...)`.
- Für neue oder refaktorierte Domain-Module Instanz-Services + Factory verwenden (z. B. `...ServicesFactory`) statt statischer Domain-Methoden.
- Keine domain-spezifischen Service-Locator-Helper in `lib/Support/helpers/app.php`; Composition per Factory im Aufrufer.
### Action (`pages/**.php`)
@@ -121,6 +123,7 @@ Ausführen bei: Feature-Entfernung, größerem Refactoring oder vor Releases. Pa
## 8) Settings-Konvention
- Neue Settings immer zuerst sauber im `SettingService` modellieren (Validierung + Read/Write).
- Nur dann in `SettingCacheService::update(...)` aufnehmen, wenn der Key wirklich über `appSetting(...)` im Hot Path genutzt wird.
- Aufrufer nutzen `SettingGateway` aus `SettingServicesFactory`; kein globaler `settingGateway()`-Helper.
- Nur dann in den `SettingCacheService` aufnehmen, wenn der Key wirklich über `appSetting(...)` im Hot Path genutzt wird.
- Keine Sicherheits-/Integrationssettings (SMTP, SSO-Secrets, API-Policies) als Pflicht in den Datei-Cache drücken.
- Bei manuellen DB-Änderungen an gecachten Keys den Cache gezielt aktualisieren (z. B. über Settings-Save-Flow).

View File

@@ -10,6 +10,8 @@ servers:
security:
- BearerAuth: []
tags:
- name: auth
description: Public authentication endpoints (no Bearer token required)
- name: me
- name: tokens
- name: users
@@ -17,6 +19,60 @@ tags:
- name: departments
- name: roles
paths:
/auth/login:
post:
tags: [auth]
summary: Login with email and password
description: |
Authenticates a user with email and password and returns a Bearer token.
This endpoint is **public** — no Authorization header required.
The returned token can be used immediately as `Authorization: Bearer <token>`
for all subsequent API requests.
Rate limits (stricter than general API limits):
- 10 failed attempts per IP per 10 minutes
- 5 failed attempts per email+IP per 15 minutes
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
example:
email: user@example.com
password: "..."
token_name: "My App"
responses:
'201':
description: Login successful — token issued
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
invalid_credentials:
value:
error: invalid_credentials
account_inactive:
value:
error: account_inactive
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/me:
get:
tags: [me]
@@ -833,6 +889,47 @@ components:
data:
type: object
additionalProperties: true
LoginRequest:
type: object
additionalProperties: false
required: [email, password]
properties:
email:
type: string
format: email
description: User email address (case-insensitive)
password:
type: string
description: User password
token_name:
type: string
description: Optional label for the issued token (e.g. "iPhone App"). Defaults to "Login".
default: Login
tenant_uuid:
type: string
format: uuid
description: Optional — scope the issued token to a specific tenant UUID. Must be assigned to the user.
LoginResponse:
type: object
required: [data]
properties:
data:
type: object
required: [token, token_uuid, expires_at]
properties:
token:
type: string
description: Full Bearer token in format `selector:plaintext`. Shown only once — store securely.
example: "abc123def:456789..."
token_uuid:
type: string
format: uuid
description: UUID of the created token record (use for revocation via /me/tokens)
expires_at:
type: string
format: date-time
nullable: true
description: UTC expiry timestamp of the token
RoleWriteRequest:
type: object
additionalProperties: false

View File

@@ -1,6 +1,6 @@
# Sicherheitsmodell
Letzte Aktualisierung: 2026-02-21
Letzte Aktualisierung: 2026-02-22
## 1) Grundprinzip
@@ -21,6 +21,8 @@ Kein einzelner Layer ist ausreichend; die Kombination ist der Schutz.
- Auto-Login aus Cookie erfolgt früh in `/web/index.php`.
- Zentraler Login-Entry ist `login` (optional `login?tenant=<slug>` für Tenant-Vorauswahl).
- Unknown-Email-Verhalten im Login bleibt generisch (keine Tenant-/Account-Details vor erfolgreicher Auflösung).
- Auth-/SSO-Kern ist instanzbasiert verdrahtet über `lib/Service/Auth/AuthServicesFactory.php`
(`AuthService`, `RememberMeService`, `SsoUserLinkService`, `TenantSsoService`, `MicrosoftOidcService`).
Kritisch: