137 lines
4.8 KiB
Markdown
137 lines
4.8 KiB
Markdown
# Slip It In – Architektur-Updates (Phase 2b & 3b)
|
||
|
||
Dieses Dokument fasst die Sicherheits- und Stabilitäts-Erweiterungen zusammen, die dem ProjectPlan.md hinzugefügt wurden.
|
||
|
||
## 🔒 Backend-Sicherheit (Phase 2b)
|
||
|
||
### 1. JWT-Claims Validation im GameHub
|
||
- **Problem**: Clients könnten falsche PlayerId-Claims senden (Spoofing)
|
||
- **Lösung**: `GetAuthenticatedUserId()` extrahiert User-ID aus JWT-Claims
|
||
- **Implementierung**: Jede Hub-Methode validiert `Context.User?.FindFirst(ClaimTypes.NameIdentifier)`
|
||
|
||
### 2. IDbContextFactory statt Scoped DbContext
|
||
- **Problem**: Bei parallelen SignalR-Aufrufen kann es zu Race Conditions kommen
|
||
- **Lösung**: `IDbContextFactory<SlipItInDbContext>` erzeugt isolierte Sessions pro Aufruf
|
||
- **Vorteil**: Phrase-Transfer ist thread-safe, keine Conflicts bei 2 gleichzeitigen Challenges
|
||
|
||
### 3. Serverseitige Validierung
|
||
- **Regel**: Clients senden nur "Ich möchte X machen", Server prüft ALLES
|
||
- **Beispiel**: `SubmitSlipAsync()` prüft auf dem Server:
|
||
- ✅ Gehört die Phrase dem Spieler?
|
||
- ✅ Ist die Runde aktiv?
|
||
- ✅ Ist der Spieler noch im Spiel?
|
||
|
||
### 4. Daten-Isolation (GameStateDto vs. PlayerHandDto)
|
||
- **GameStateDto**: Öffentliche Daten (Username, Score, CardCount) → für ALLE sichtbar
|
||
- **PlayerHandDto**: Private Daten (Text der eigenen Karten) → nur für den Spieler selbst
|
||
- **Ergebnis**: Gegner sehen nicht, welche Phrasen ich habe!
|
||
|
||
---
|
||
|
||
## 📱 Client-Stabilität (Phase 3b)
|
||
|
||
### 1. WeakReferenceMessenger für Event-Entkopplung
|
||
- **Problem**: Services rufen ViewModels direkt auf → Memory Leaks wenn VM disposed
|
||
- **Lösung**: Services senden Messages via `WeakReferenceMessenger.Default.Send()`
|
||
- **Vorteil**: ViewModels können sich abmelden ohne zirkuläre Abhängigkeiten
|
||
|
||
**Beispiel:**
|
||
```csharp
|
||
// Service sendet Event
|
||
WeakReferenceMessenger.Default.Send(new GameStateChangedMessage(newState));
|
||
|
||
// ViewModel empfängt (mit auto-cleanup beim Dispose)
|
||
WeakReferenceMessenger.Default.Register<GameStateChangedMessage>(this, (r, m) =>
|
||
{
|
||
CurrentGameState = m.Value;
|
||
});
|
||
```
|
||
|
||
### 2. Automatischer Reconnect
|
||
- **Strategie**: Exponential Backoff (0s → 2s → 10s → 30s)
|
||
- **Pufferung**: Offline-Aktionen werden in lokale Queue geschrieben
|
||
- **Resync**: Nach Reconnect wird `ResyncAsync()` aufgerufen
|
||
|
||
**Flow:**
|
||
1. SignalR-Verbindung getrennt
|
||
2. Client speichert "Slip zu Phrase X" in lokale Queue
|
||
3. Nach 2s Automatischer Reconnect-Versuch
|
||
4. Nach erfolgreichem Reconnect: Queued Actions werden abgesendet
|
||
|
||
### 3. State Restoration
|
||
- **App-Pause**: `GameStateService` speichert aktuellen State in `SecureStorage`
|
||
- **App-Resume**: `ResyncAsync()` wird aufgerufen
|
||
- ✅ Holt aktuellen GameState vom Server
|
||
- ✅ Sendet gepufferte Offline-Aktionen erneut
|
||
- ✅ UI zeigt "Syncing..." bis fertig
|
||
|
||
### 4. Dual-Layer Storage
|
||
- **In-Memory Layer**: `GameStateService` halten aktuellen State
|
||
- **Persistent Layer**: `SecureStorage`/`Preferences` speichern Backup
|
||
- **Fehler-Handling**: Nach App-Crash kann State aus Persistent Layer wiederhergestellt werden
|
||
|
||
---
|
||
|
||
## 📊 Schnellreferenz: Neue Components
|
||
|
||
### Backend
|
||
```
|
||
GameHub
|
||
├─ GetAuthenticatedUserId() → JWT-Validierung
|
||
├─ ValidatePlayerAccessAsync() → Authorization Check
|
||
└─ Alle Methoden nutzen IDbContextFactory
|
||
|
||
Program.cs
|
||
├─ AddDbContextFactory<SlipItInDbContext>()
|
||
├─ AddAuthentication(JwtBearerDefaults)
|
||
└─ AddSignalR() mit JWT-Filter
|
||
|
||
DTOs
|
||
├─ GameStateDto (öffentlich)
|
||
├─ PlayerHandDto (privat)
|
||
└─ Keine CardText in GameStateDto!
|
||
```
|
||
|
||
### Client
|
||
```
|
||
GameStateService
|
||
├─ CurrentGameState (in-memory)
|
||
├─ CurrentPlayerHand (in-memory)
|
||
├─ UpdateGameStateAsync() → speichert + sendet Message
|
||
└─ ResyncAsync() → bei App-Resume
|
||
|
||
SignalRService
|
||
├─ ConnectAsync(token) mit Auto-Reconnect
|
||
├─ RequestGameStateAsync()
|
||
└─ On<> Handler senden Messages
|
||
|
||
Messages (WeakReferenceMessenger)
|
||
├─ GameStateChangedMessage
|
||
├─ PlayerHandChangedMessage
|
||
└─ ChallengeReceivedMessage
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Wichtigste Tests
|
||
|
||
| Test | Ziel |
|
||
|------|------|
|
||
| **JWT-Auth** | Hub lehnt Requests ohne gültiges Token ab |
|
||
| **Player-Access** | User A kann nicht auf Players von User B zugreifen |
|
||
| **Race Condition** | Phrase-Transfer ist thread-safe bei 2 gleichzeitigen Challenges |
|
||
| **Offline-Queue** | Aktionen während Netzwerk-Ausfall werden gepuffert und später gesendet |
|
||
| **Memory Leaks** | Keine Leaks wenn ViewModels disposed werden (WeakReference) |
|
||
| **State-Sync** | App-Resume synct korrekt mit Server-State |
|
||
|
||
---
|
||
|
||
## 🚀 Implementierungs-Reihenfolge
|
||
|
||
1. **Backend zuerst**: JWT-Auth + IDbContextFactory konfigurieren
|
||
2. **GameService erweitern**: Serverseitige Validierung in alle Methoden
|
||
3. **Client-Services**: GameStateService + SignalRService implementieren
|
||
4. **Messaging**: WeakReferenceMessenger in ViewModels integrieren
|
||
5. **Tests**: Integrationstest-Suite schreiben
|
||
6. **UI**: Views an neue Message-Events binden
|