# User Management - Dokumentation **Bounded Context:** User Management (Generic Subdomain) **Architektur:** DDD-Light + Clean Architecture **Status:** MVP Implementation Complete --- ## Übersicht User Management ist als **Generic Subdomain** klassifiziert - Commodity-Funktionalität ohne Wettbewerbsvorteil. Daher minimaler DDD-Aufwand: - ✅ Einfache Entities (keine Aggregates) - ✅ Transaction Script Pattern (keine komplexen Domain Services) - ✅ Keine Domain Events - ✅ Standard-Technologien (Spring Security, JWT, BCrypt) ### Warum wird User Management benötigt? - **HACCP-Compliance:** Alle Qualitätsaktionen müssen einem Benutzer zugeordnet sein - **GoBD-Compliance:** Audit Trail für alle geschäftskritischen Aktionen - **Mehrfilialen-Support:** Benutzer müssen Filialen zugeordnet werden - **Berechtigungskonzept:** Unterschiedliche Rollen für Produktion, Qualität, Verkauf --- ## Architektur ### Clean Architecture Layers ``` ┌─────────────────────────────────────────────────────┐ │ REST API (Controllers) │ │ AuthController, UserController, RoleController │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Application Layer (Use Cases) │ │ CreateUser, AuthenticateUser, ChangePassword │ │ Transaction Script Pattern │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Domain Layer (DDD-Light) │ │ User, Role (Simple Entities) │ │ UserId, RoleId, PasswordHash (Value Objects) │ │ UserRepository, RoleRepository (Interfaces) │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Infrastructure Layer │ │ Spring Security, JWT, BCrypt, JPA, PostgreSQL │ └─────────────────────────────────────────────────────┘ ``` ### Wichtige Design-Entscheidungen | Entscheidung | Begründung | |--------------|------------| | **Eigene Implementation** statt Keycloak | Einfacher für MVP, volle Kontrolle, weniger Komplexität | | **JWT** statt Sessions | Stateless, skalierbar, microservice-ready | | **BCrypt Strength 12** | Sicher (~250ms), nicht zu langsam | | **AuthorizationPort** (ACL) | Ermöglicht spätere Keycloak-Migration ohne BC-Änderungen | | **Async Audit Logging** | Performance - blockiert Use Cases nicht | | **No Domain Events** | Generic Subdomain - minimaler DDD-Aufwand | --- ## Domain Model ### Entities #### User ```java public class User { private UserId id; private String username; private String email; private PasswordHash passwordHash; // BCrypt private Set roles; private String branchId; // Filialzuordnung private UserStatus status; // ACTIVE, INACTIVE, LOCKED private LocalDateTime createdAt; private LocalDateTime lastLogin; } ``` #### Role (Reference Data) ```java public class Role { private RoleId id; private RoleName name; // Enum: ADMIN, PRODUCTION_MANAGER, ... private Set permissions; private String description; } ``` ### Value Objects - `UserId` - UUID-basiert - `RoleId` - UUID-basiert - `PasswordHash` - BCrypt Hash (60 chars, $2a$12$...) - `Permission` - Enum (RECIPE_READ, BATCH_WRITE, etc.) - `RoleName` - Enum (ADMIN, PRODUCTION_MANAGER, etc.) --- ## Vordefinierte Rollen | Rolle | Permissions | Zielgruppe | |-------|-------------|------------| | **ADMIN** | Alle (67) | Systemadministrator | | **PRODUCTION_MANAGER** | RECIPE_*, BATCH_*, PRODUCTION_ORDER_*, STOCK_READ | Leiter Produktion | | **PRODUCTION_WORKER** | RECIPE_READ, BATCH_*, LABEL_* | Produktionsmitarbeiter | | **QUALITY_MANAGER** | HACCP_*, TEMPERATURE_LOG_*, CLEANING_RECORD_*, GOODS_INSPECTION_* | Qualitätsbeauftragter | | **QUALITY_INSPECTOR** | TEMPERATURE_LOG_*, GOODS_INSPECTION_* | QM-Mitarbeiter | | **PROCUREMENT_MANAGER** | PURCHASE_ORDER_*, GOODS_RECEIPT_*, SUPPLIER_* | Einkaufsleiter | | **WAREHOUSE_WORKER** | STOCK_*, INVENTORY_COUNT_*, LABEL_* | Lagermitarbeiter | | **SALES_MANAGER** | ORDER_*, INVOICE_*, CUSTOMER_* | Verkaufsleiter | | **SALES_STAFF** | ORDER_READ/WRITE, CUSTOMER_READ, STOCK_READ | Verkaufsmitarbeiter | Rollen werden bei DB-Initialisierung aus Seed-Daten geladen (Flyway Migration V002). --- ## Authorization Port (ACL für andere BCs) ### Warum AuthorizationPort? **Problem:** Andere BCs brauchen Authentifizierung/Autorisierung, sollen aber nicht direkt von User Management abhängen. **Lösung:** `AuthorizationPort` Interface im **Shared Kernel** - typsicher, action-oriented, ermöglicht Keycloak-Migration. ### Interface ```java public interface AuthorizationPort { boolean can(Action action); void assertCan(Action action); boolean can(Action action, ResourceId resource); void assertCan(Action action, ResourceId resource); ActorId currentActor(); Optional currentBranch(); } ``` ### Typsichere Actions (Sealed Interface + Enums) ```java // Shared Kernel - Basis-Interface public interface Action { // Marker interface } // Production BC - eigene Actions public enum ProductionAction implements Action { RECIPE_READ, RECIPE_WRITE, RECIPE_DELETE, BATCH_READ, BATCH_WRITE, BATCH_COMPLETE, PRODUCTION_ORDER_READ, PRODUCTION_ORDER_WRITE } // Quality BC public enum QualityAction implements Action { TEMPERATURE_LOG_READ, TEMPERATURE_LOG_WRITE, CLEANING_RECORD_READ, CLEANING_RECORD_WRITE, GOODS_INSPECTION_READ, GOODS_INSPECTION_WRITE } ``` ### Nutzung in anderen BCs ```java // Production BC - Rezept erstellen public class CreateRecipe { private final AuthorizationPort authPort; private final RecipeRepository recipeRepository; private final AuditLogger auditLogger; public Result execute(CreateRecipeCommand cmd) { // ✅ Typsichere fachliche Authorization authPort.assertCan(ProductionAction.RECIPE_WRITE); // Business logic Recipe recipe = Recipe.create(cmd.name(), cmd.ingredients()); recipeRepository.save(recipe); // Audit logging mit ActorId auditLogger.log( AuditEvent.RECIPE_CREATED, recipe.id(), authPort.currentActor() ); return Result.success(RecipeDTO.from(recipe)); } } ``` ### Action → Permission Mapping ```java // Infrastructure Layer - ActionToPermissionMapper @Component public class ActionToPermissionMapper { public Permission mapActionToPermission(Action action) { // Type-safe pattern matching (Java 21) if (action instanceof ProductionAction pa) { return switch (pa) { case RECIPE_READ -> Permission.RECIPE_READ; case RECIPE_WRITE -> Permission.RECIPE_WRITE; // ... exhaustive }; } // ... andere BCs } } ``` ### Vorteile 1. ✅ **Typsicher** - Compiler prüft Actions, keine Tippfehler 2. ✅ **Fachlich** - Jeder BC spricht seine eigene Sprache 3. ✅ **Entkoppelt** - BCs kennen keine User/Roles/Permissions 4. ✅ **Flexibel** - Action-zu-Permission-Mapping änderbar ohne BC-Änderungen 5. ✅ **IDE-Support** - Auto-Completion für Actions 6. ✅ **Keycloak-ready** - Actions können auf Keycloak Policies gemappt werden --- ## REST API ### Authentication Endpoints (Public) #### Login ```http POST /api/auth/login Content-Type: application/json { "username": "admin", "password": "admin123" } Response 200 OK: { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "tokenType": "Bearer", "expiresIn": 28800, "expiresAt": "2026-02-18T02:00:00", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` #### Logout ```http POST /api/auth/logout Authorization: Bearer {accessToken} Response 204 No Content ``` #### Refresh Token ```http POST /api/auth/refresh Content-Type: application/json { "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } Response 200 OK: { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", ... } ``` ### User Management Endpoints (Authenticated) #### Create User (ADMIN only) ```http POST /api/users Authorization: Bearer {accessToken} Content-Type: application/json { "username": "jdoe", "email": "jdoe@example.com", "password": "SecurePass123!", "roleNames": ["PRODUCTION_WORKER"], "branchId": "branch-123" } Response 201 Created ``` #### List Users ```http GET /api/users Authorization: Bearer {accessToken} Response 200 OK: [ { "id": "user-123", "username": "jdoe", "email": "jdoe@example.com", "roles": [...], "branchId": "branch-123", "status": "ACTIVE", "createdAt": "2026-02-17T10:00:00", "lastLogin": "2026-02-17T14:30:00" } ] ``` #### Lock/Unlock User (ADMIN only) ```http POST /api/users/{id}/lock POST /api/users/{id}/unlock Authorization: Bearer {accessToken} Response 200 OK ``` #### Change Password ```http PUT /api/users/{id}/password Authorization: Bearer {accessToken} Content-Type: application/json { "currentPassword": "OldPass123!", "newPassword": "NewPass456!" } Response 204 No Content ``` ### Swagger UI Interaktive API-Dokumentation: **http://localhost:8080/swagger-ui/index.html** --- ## Security ### JWT Token Structure ```json { "sub": "user-123", "username": "jdoe", "permissions": ["RECIPE_READ", "BATCH_WRITE", ...], "branchId": "branch-123", "iat": 1708185600, "exp": 1708214400 } ``` - **Algorithm:** HS256 (HMAC-SHA256) - **Secret:** Konfiguriert via `jwt.secret` in application.yml - **Expiration:** 8 Stunden (Access Token), 7 Tage (Refresh Token) ### Password Security - **Algorithm:** BCrypt - **Strength:** 12 Rounds (~250ms hashing time) - **Requirements:** Min. 8 Zeichen, 1 Großbuchstabe, 1 Kleinbuchstabe, 1 Zahl, 1 Sonderzeichen ### Spring Security Configuration ```yaml # Stateless JWT Authentication - sessionManagement: STATELESS - CSRF: Disabled (safe for stateless JWT) - Public Endpoints: /api/auth/login, /api/auth/refresh - Protected Endpoints: /api/** (require authentication) ``` --- ## Audit Logging (HACCP/GoBD) ### Compliance Requirements - **HACCP:** Wer hat wann welche Temperatur gemessen? - **GoBD:** Unveränderlicher Audit Trail für 10 Jahre - **Retention:** 10 Jahre (gesetzlich), dann archivieren ### Audit Log Structure ```sql CREATE TABLE audit_logs ( id VARCHAR(36) PRIMARY KEY, event VARCHAR(100) NOT NULL, -- z.B. USER_CREATED, TEMPERATURE_RECORDED entity_id VARCHAR(36), -- z.B. UserId, RecipeId, BatchId performed_by VARCHAR(36), -- ActorId details VARCHAR(2000), -- Zusätzliche Details timestamp TIMESTAMP NOT NULL, -- Wann (Business-Zeit) ip_address VARCHAR(45), -- Woher (Client IP) user_agent VARCHAR(500), -- Womit (Browser/App) created_at TIMESTAMP NOT NULL -- DB-Insert-Zeit ); ``` ### Audit Events - **User Management:** USER_CREATED, LOGIN_SUCCESS, PASSWORD_CHANGED - **Quality BC:** TEMPERATURE_RECORDED, TEMPERATURE_CRITICAL, CLEANING_PERFORMED - **Production BC:** BATCH_CREATED, BATCH_COMPLETED, RECIPE_MODIFIED - **System:** SYSTEM_SETTINGS_CHANGED ### Performance - **Async Logging:** `@Async` - blockiert Use Cases nicht - **Separate Transaction:** `REQUIRES_NEW` - auch bei Business-TX-Rollback - **Batch-Inserts:** Möglich für High-Volume-Szenarien --- ## Branch-Zuordnung (Mehrfilialen) ### User-Branch Beziehung ```java public class User { private String branchId; // Optional, null = ADMIN (global access) } ``` ### Data Filtering **Strategie 1: Application Layer Filtering (MVP)** ```java public class ListStock { private final AuthorizationPort authPort; public Result> execute() { Optional actorBranch = authPort.currentBranch(); if (actorBranch.isPresent()) { // Normaler User: Nur eigene Filiale return Result.success(stockRepository.findByBranch(actorBranch.get())); } else { // ADMIN: Alle Filialen return Result.success(stockRepository.findAll()); } } } ``` **Strategie 2: Database Row-Level Security (Post-MVP)** ```sql -- PostgreSQL RLS (später) CREATE POLICY user_branch_policy ON stock USING (branch_id = current_setting('app.current_branch_id')::text); ``` --- ## Deployment ### Umgebungsvariablen ```bash # JWT Configuration JWT_SECRET=YourVerySecretKeyMin256BitsForHS256Algorithm # Min. 256 Bits! # Database SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/effigenix SPRING_DATASOURCE_USERNAME=effigenix SPRING_DATASOURCE_PASSWORD=effigenix # Flyway SPRING_FLYWAY_ENABLED=true ``` ### Docker Compose (PostgreSQL) ```yaml version: '3.8' services: postgres: image: postgres:15 environment: POSTGRES_DB: effigenix POSTGRES_USER: effigenix POSTGRES_PASSWORD: effigenix ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data: ``` ### Build & Run ```bash # Build mvn clean package # Run java -jar target/effigenix-erp-0.1.0-SNAPSHOT.jar # Run mit Production Profile java -jar target/effigenix-erp-0.1.0-SNAPSHOT.jar --spring.profiles.active=prod ``` --- ## Entwickler-Guide ### Neue Rolle hinzufügen 1. **RoleName Enum erweitern** (`domain/usermanagement/RoleName.java`) 2. **Permissions zuweisen** in Flyway Seed-Daten (`V002__seed_roles_and_permissions.sql`) 3. **Migration ausführen** oder manuell in DB einfügen ### AuthorizationPort in neuem BC nutzen 1. **Action Enum erstellen** (z.B. `NewBCAction`) ```java public enum NewBCAction implements Action { ENTITY_READ, ENTITY_WRITE, ENTITY_DELETE } ``` 2. **Action in Shared Kernel registrieren** (nur für Dokumentation, nicht sealed) 3. **Mapping hinzufügen** in `ActionToPermissionMapper` ```java if (action instanceof NewBCAction nba) { return switch (nba) { case ENTITY_READ -> Permission.ENTITY_READ; case ENTITY_WRITE -> Permission.ENTITY_WRITE; case ENTITY_DELETE -> Permission.ENTITY_DELETE; }; } ``` 4. **Permissions zu RoleName Enum hinzufügen** ```java public enum Permission { // ... existing ENTITY_READ, ENTITY_WRITE, ENTITY_DELETE } ``` 5. **In Use Cases nutzen** ```java authPort.assertCan(NewBCAction.ENTITY_WRITE); ``` ### Custom Audit Event hinzufügen 1. **AuditEvent Enum erweitern** ```java public enum AuditEvent { // ... existing MY_CUSTOM_EVENT } ``` 2. **In Use Case loggen** ```java auditLogger.log(AuditEvent.MY_CUSTOM_EVENT, entityId, actorId); ``` --- ## Testing ### Manuelles Testen (Swagger UI) 1. **Server starten:** `mvn spring-boot:run` 2. **Swagger öffnen:** http://localhost:8080/swagger-ui/index.html 3. **Login:** POST /api/auth/login mit `admin` / `admin123` (Seed-Daten erstellen) 4. **Token kopieren** aus Response 5. **Authorize** Button klicken, Token einfügen: `Bearer {token}` 6. **Endpoints testen** ### curl Beispiele ```bash # Login curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' # Get Users (mit Token) curl -X GET http://localhost:8080/api/users \ -H "Authorization: Bearer {accessToken}" ``` --- ## Migration zu Keycloak (Zukunft) ### Warum ACL-Pattern vorbereitet ist **Aktuell:** `SpringSecurityAuthorizationAdapter` → User Management BC **Zukunft:** `KeycloakAuthorizationAdapter` → Keycloak ### Migrations-Schritte 1. **KeycloakAuthorizationAdapter implementieren** - Implementiert `AuthorizationPort` - Mappt Actions auf Keycloak Policies/Roles 2. **Spring Configuration umstellen** - Keycloak Adapter in `SecurityConfig` einbinden - JWT Validation auf Keycloak-Token umstellen 3. **Keine Änderungen in BCs nötig!** - BCs nutzen weiterhin `authPort.assertCan(Action)` - Mapping bleibt gleich --- ## Performance Considerations | Komponente | Performance | Optimierung | |------------|-------------|-------------| | **Password Hashing** | ~250ms (BCrypt 12) | Akzeptabel für Login, async für bulk operations | | **JWT Validation** | <1ms | Stateless, keine DB-Abfrage | | **Audit Logging** | Async | Blockiert Use Cases nicht | | **Authorization Check** | <5ms | In-Memory Permissions (aus JWT) | ### Produktions-Empfehlungen 1. **JWT Secret:** Min. 256 Bits, rotieren alle 90 Tage 2. **Refresh Token:** Redis-backed statt In-Memory 3. **Audit Logs:** Archivierung nach 10 Jahren (Legal-Hold) 4. **Rate Limiting:** Login-Endpunkt schützen (5 Versuche / 15 Min) 5. **HTTPS:** Nur HTTPS in Produktion (JWT im Header!) --- ## Troubleshooting ### "Invalid JWT signature" - **Ursache:** JWT Secret geändert oder nicht konfiguriert - **Lösung:** `JWT_SECRET` Umgebungsvariable prüfen ### "User not found" bei Login - **Ursache:** Seed-Daten nicht geladen - **Lösung:** Flyway Migrations ausführen (`mvn flyway:migrate`) ### "Access Denied" trotz richtiger Rolle - **Ursache:** Permission fehlt in `ActionToPermissionMapper` - **Lösung:** Mapping hinzufügen und neu deployen ### Audit Logs werden nicht geschrieben - **Ursache:** Async nicht konfiguriert - **Lösung:** `@EnableAsync` in Application Class prüfen --- ## Fazit User Management als **Generic Subdomain** mit: ✅ **Minimaler DDD-Aufwand** - Einfache Entities, Transaction Scripts ✅ **Typsichere Authorization** - Action Enums statt String-Permissions ✅ **ACL-Pattern** - Ermöglicht Keycloak-Migration ✅ **HACCP/GoBD-Compliant** - Audit Logging mit 10-Jahres-Retention ✅ **Production-Ready** - JWT, BCrypt, Async, Multi-Branch **Nächste Schritte:** 1. Seed-Daten für initialen Admin-User erstellen 2. Integration Tests schreiben 3. Production-Konfiguration (HTTPS, Rate Limiting, Redis) 4. Keycloak-Migration evaluieren (wenn OAuth2/SSO benötigt)