# 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<Role> 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<Permission> 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<BranchId> 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<ApplicationError, RecipeDTO> 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<ApplicationError, List<StockDTO>> execute() {
        Optional<BranchId> 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)
