1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 17:04:49 +01:00

refactor: restructure repository with separate backend and frontend directories

- Move Java backend to backend/ directory
- Create frontend/ directory for TypeScript TUI and future WebUI
- Update .gitignore for Node.js and worktrees
- Update README.md with new repository structure
- Copy documentation to backend/
This commit is contained in:
Sebastian Frick 2026-02-17 22:08:51 +01:00
parent ec9114aa0a
commit c2c48a03e8
141 changed files with 734 additions and 9 deletions

View file

@ -0,0 +1,292 @@
# Ticket 001 Code Review: User Management Bounded Context
**Datum:** 2026-02-17
**Commit:** `ec9114a` feat: add Spring Boot ERP application with user management domain
**Status:** Offen
---
## Kritisch
### K1 Aggregates sind nicht immutable
**Betroffene Dateien:**
- `domain/usermanagement/User.java` (Zeilen 2229)
- `domain/usermanagement/Role.java` (Zeilen 1821)
**Problem:**
Felder in `User` und `Role` sind nicht `final`. Business-Methoden wie `lock()`, `unlock()`, `updateEmail()`, `changePassword()` mutieren den internen State direkt statt neue Instanzen oder `Result` zurückzugeben. Die interne `roles`-Collection in `User` (Zeile 25) ist mutable und wird per `assignRole()`/`removeRole()` in-place modifiziert.
**Lösung:**
- Alle Felder `final` machen
- Business-Methoden geben `Result<UserError, User>` zurück (Copy-on-Write)
- Interne Collections nur über defensive Kopien exponieren
---
### K2 Fehlende Command-Validierung im Application Layer
**Betroffene Dateien:**
- `application/usermanagement/command/CreateUserCommand.java`
- `application/usermanagement/command/UpdateUserCommand.java`
- `application/usermanagement/command/AuthenticateCommand.java`
- `application/usermanagement/command/ChangePasswordCommand.java`
- `application/usermanagement/command/AssignRoleCommand.java`
**Problem:**
Alle Commands sind reine Records ohne jegliche Validierung. Wird der Application Layer außerhalb des HTTP-Kontexts aufgerufen (z.B. Scheduled Jobs, Message Queues), gibt es keine Input-Checks. Die Validierung liegt ausschließlich in den Web-DTOs (`@Valid`-Annotationen).
**Lösung:**
- Self-validating Commands mit Factory-Methoden, die `Result` zurückgeben
- Oder Validierung als ersten Schritt im Use Case
---
### K3 Authorization fehlt im Application Layer
**Betroffene Dateien:**
- `application/usermanagement/CreateUser.java`
- `application/usermanagement/LockUser.java`
- `application/usermanagement/AssignRole.java`
- (alle Use Cases betroffen)
**Problem:**
`AuthorizationPort` wird im Application Layer nicht verwendet. Autorisierung liegt ausschließlich auf `@PreAuthorize` im Controller. Bei Non-HTTP-Aufrufen wird die Authorization komplett umgangen.
**Lösung:**
- `AuthorizationPort.can(actorId, action)` als ersten Check in jedem Use Case aufrufen
- Bei fehlender Berechtigung `Result.failure(UserError.Unauthorized(...))` zurückgeben
---
### K4 Token-Blacklist Memory Leak
**Betroffene Datei:**
- `infrastructure/security/JwtSessionManager.java` (Zeilen 3536, 164168)
**Problem:**
Die Token-Blacklist nutzt `ConcurrentHashMap.newKeySet()` ohne TTL oder Cleanup. Bei laufendem Server wächst die Blacklist unbegrenzt. Nach Server-Restart ist die Blacklist leer ausgeloggte Tokens werden wieder gültig. Funktioniert nicht in Cluster-Deployments.
**Lösung:**
- Redis-basierte Blacklist mit TTL = Token-Expiration
- Oder In-Memory mit Scheduled Cleanup abgelaufener Tokens
---
### K5 Audit-Bug in ChangePassword
**Betroffene Datei:**
- `application/usermanagement/ChangePassword.java` (Zeile 52)
**Problem:**
Im Fehlerfall wird `AuditEvent.PASSWORD_CHANGED` geloggt statt `PASSWORD_CHANGE_FAILED`. Falsche Audit-Events verfälschen das Security-Monitoring.
**Lösung:**
- Korrektes Event `PASSWORD_CHANGE_FAILED` im Fehlerfall loggen
- Audit-Events auf Success/Failure-Pfade prüfen
---
## Mittel
### M1 Fehlende Status-Transitionen-Validierung
**Betroffene Datei:**
- `domain/usermanagement/User.java` (Zeilen 118132)
**Problem:**
`lock()`, `unlock()`, `deactivate()`, `activate()` validieren den aktuellen Status nicht. Invalide Übergänge sind möglich (z.B. einen inaktiven User sperren). Keine Idempotenz-Checks.
**Lösung:**
- State-Machine oder explizite Guards für erlaubte Übergänge
- `Result<UserError, User>` als Rückgabetyp
---
### M2 Inkonsistente Result-Nutzung in Domain-Methoden
**Betroffene Datei:**
- `domain/usermanagement/User.java` (Zeilen 106159)
**Problem:**
Manche Business-Methoden geben `Result` zurück (`assignRole()`, `changePassword()`), andere `void` (`removeRole()`, `lock()`, `unlock()`, `updateBranch()`, `updateLastLogin()`). Inkonsistentes API-Design.
**Lösung:**
- Alle Business-Methoden einheitlich auf `Result<UserError, User>` umstellen (siehe K1)
---
### M3 RemoveRole bricht Command-Pattern
**Betroffene Datei:**
- `application/usermanagement/RemoveRole.java` (Zeile 44)
**Problem:**
Einziger Use Case, der Raw-Parameter statt eines Command-Objekts akzeptiert:
```java
public Result<UserError, UserDTO> execute(String userId, RoleName roleName, ActorId performedBy)
```
Alle anderen Use Cases nutzen Command-Records.
**Lösung:**
- `RemoveRoleCommand` Record erstellen und in `execute()` verwenden
---
### M4 Refresh Token nicht implementiert
**Betroffene Datei:**
- `infrastructure/security/JwtSessionManager.java` (Zeilen 141144)
**Problem:**
`refreshSession()` wirft `UnsupportedOperationException`. User-Status (gesperrt/gelöscht) wird beim Token-Refresh nicht geprüft.
**Lösung:**
- Implementierung: User laden, Status verifizieren, neues Token-Paar ausstellen
- Altes Refresh-Token auf Blacklist setzen
---
### M5 Kein Rate Limiting auf Login-Endpoint
**Betroffene Datei:**
- `infrastructure/usermanagement/web/controller/AuthController.java` (Zeile 90)
**Problem:**
`/api/auth/login` hat keinen Brute-Force-Schutz. Angreifer können Credentials unbegrenzt durchprobieren.
**Lösung:**
- Rate Limiting per IP/Username (z.B. Bucket4j, Spring Cloud Gateway)
- Account-Lockout nach N fehlgeschlagenen Versuchen
- Exponentielles Backoff
---
### M6 CORS deaktiviert statt konfiguriert
**Betroffene Datei:**
- `infrastructure/security/SecurityConfig.java` (Zeile 69)
**Problem:**
CORS ist komplett deaktiviert (`AbstractHttpConfigurer::disable`). TODO-Kommentar im Code, aber keine Konfiguration. Browser-basierte Clients können keine Cross-Origin-Requests senden.
**Lösung:**
- CORS mit expliziten Allowed Origins, Methods und Headers konfigurieren
- Environment-abhängig (Dev: localhost, Prod: spezifische Domain)
---
### M7 Error Response Information Disclosure
**Betroffene Datei:**
- `infrastructure/security/SecurityConfig.java` (Zeilen 100113)
**Problem:**
Custom Exception Handler gibt `authException.getMessage()` direkt im JSON-Response zurück. Kann interne Implementierungsdetails exponieren.
**Lösung:**
- Generische Fehlermeldungen für Authentication/Authorization Failures
- Details nur ins Server-Log schreiben
---
## Niedrig
### N1 Swagger UI öffentlich zugänglich
**Betroffene Datei:**
- `infrastructure/security/SecurityConfig.java` (Zeile 83)
- `application.yml` (Zeile 56)
**Problem:**
Swagger UI ist standardmäßig ohne Authentifizierung erreichbar. Exponiert alle API-Endpoints mit Parametern.
**Lösung:**
- In Production deaktivieren oder hinter Authentication legen
- Spring-Profile nutzen: `springdoc.swagger-ui.enabled=${SWAGGER_ENABLED:false}`
---
### N2 Default-Admin Credentials in Seed-Daten
**Betroffene Datei:**
- `db/changelog/changes/004-seed-admin-user.sql` (Zeilen 23, 26)
**Problem:**
Admin-Credentials `admin/admin123` stehen als Kommentar in der SQL-Datei und als Table-Comment in der Datenbank. Bei versehentlicher DB-Exposure sind Credentials sofort sichtbar.
**Lösung:**
- Seed-Daten nur in Dev-Profil ausführen
- Table-Comment mit Credentials entfernen
- Production: Admin über Environment-Variablen oder Init-Script bootstrappen
---
### N3 CreateUser erlaubt User ohne Rollen
**Betroffene Datei:**
- `application/usermanagement/CreateUser.java` (Zeile 76)
**Problem:**
Wenn `cmd.roleNames()` leer ist, wird ein User ohne Rollen erstellt. Kein Check, ob mindestens eine Rolle zugewiesen wird.
**Lösung:**
- Validierung in Command oder Use Case: mindestens eine Rolle erforderlich
---
### N4 Falscher Error-Typ in Role.addPermission()
**Betroffene Datei:**
- `domain/usermanagement/Role.java` (Zeile 70)
**Problem:**
Bei `null`-Permission wird `UserError.NullRole()` verwendet semantisch falsch.
**Lösung:**
- Eigenen Error-Typ verwenden oder `IllegalArgumentException` (da interner Programmierfehler)
---
### N5 Fehlende Uniqueness-Validierung im Domain Layer
**Betroffene Datei:**
- `domain/usermanagement/User.java` (Zeilen 6469)
**Problem:**
Username- und Email-Uniqueness wird nur im Application Layer geprüft (Repository-Abfrage). Der Domain Layer dokumentiert diese Invariante nicht explizit.
**Lösung:**
- Domain Service für Uniqueness-Checks oder explizite Dokumentation als Application-Layer-Invariante
---
### N6 Repetitiver Switch-Pattern in Use Cases
**Betroffene Dateien:**
- Alle Use Cases im Application Layer
**Problem:**
Das Pattern `switch (repository.method()) { case Failure f -> ...; case Success s -> ...; }` wiederholt sich ~30+ Mal. Hoher Boilerplate-Anteil.
**Lösung:**
- `Result.flatMap()` und `Result.mapError()` konsequent nutzen
- Helper-Methode für Repository-Error → UserError Mapping
---
## Checkliste Production-Readiness
- [ ] K1: Aggregates immutable machen
- [ ] K2: Command-Validierung im Application Layer
- [ ] K3: AuthorizationPort in Use Cases integrieren
- [ ] K4: Token-Blacklist mit TTL/Redis ersetzen
- [ ] K5: Audit-Bug in ChangePassword fixen
- [ ] M4: Refresh Token implementieren
- [ ] M5: Rate Limiting einrichten
- [ ] M6: CORS konfigurieren
- [ ] N1: Swagger in Production absichern
- [ ] N2: Seed-Daten Production-safe machen
- [ ] JWT_SECRET, DB_PASSWORD als Environment-Variablen setzen
- [ ] HTTPS/TLS konfigurieren
- [ ] Log-Aggregation und Security-Monitoring aufsetzen