Initial commit
Dieser Commit ist enthalten in:
342
docs/CLEAN_ARCHITECTURE.md
Normale Datei
342
docs/CLEAN_ARCHITECTURE.md
Normale Datei
@ -0,0 +1,342 @@
|
||||
# Clean Architecture Design - AccountForger
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation beschreibt die saubere Architektur für das AccountForger-System mit Fokus auf das Fingerprint-Management und die Login-Funktionalität mit gespeicherten Fingerprints.
|
||||
|
||||
## Architektur-Schichten
|
||||
|
||||
### 1. Domain Layer (Innerster Kreis)
|
||||
**Keine Abhängigkeiten nach außen!**
|
||||
|
||||
```
|
||||
domain/
|
||||
├── entities/
|
||||
│ ├── account.py # Account Entity
|
||||
│ ├── browser_fingerprint.py # Fingerprint Entity
|
||||
│ └── browser_session.py # Session Entity
|
||||
├── value_objects/
|
||||
│ ├── fingerprint_id.py # Eindeutige Fingerprint-ID
|
||||
│ ├── account_id.py # Eindeutige Account-ID
|
||||
│ └── session_data.py # Session-Daten (Cookies, Storage)
|
||||
└── repositories/ # Interfaces (Abstrakte Klassen)
|
||||
├── fingerprint_repository.py
|
||||
├── account_repository.py
|
||||
└── session_repository.py
|
||||
```
|
||||
|
||||
### 2. Application Layer
|
||||
**Orchestriert Use Cases, kennt Domain**
|
||||
|
||||
```
|
||||
application/
|
||||
├── use_cases/
|
||||
│ ├── create_account/
|
||||
│ │ ├── create_account_use_case.py
|
||||
│ │ ├── create_account_dto.py
|
||||
│ │ └── create_account_presenter.py
|
||||
│ ├── login_account/
|
||||
│ │ ├── login_with_fingerprint_use_case.py
|
||||
│ │ ├── login_dto.py
|
||||
│ │ └── login_presenter.py
|
||||
│ └── manage_fingerprint/
|
||||
│ ├── generate_fingerprint_use_case.py
|
||||
│ ├── save_fingerprint_use_case.py
|
||||
│ └── load_fingerprint_use_case.py
|
||||
└── services/
|
||||
├── fingerprint_manager.py # Orchestriert Fingerprint-Operationen
|
||||
└── session_manager.py # Verwaltet Browser-Sessions
|
||||
```
|
||||
|
||||
### 3. Infrastructure Layer
|
||||
**Implementiert Interfaces aus Domain**
|
||||
|
||||
```
|
||||
infrastructure/
|
||||
├── persistence/
|
||||
│ ├── sqlite/
|
||||
│ │ ├── sqlite_fingerprint_repository.py
|
||||
│ │ ├── sqlite_account_repository.py
|
||||
│ │ └── sqlite_session_repository.py
|
||||
│ └── migrations/
|
||||
│ └── fingerprint_schema.sql
|
||||
├── browser/
|
||||
│ ├── playwright_adapter.py # Adapter für Playwright
|
||||
│ ├── fingerprint_injector.py # Injiziert Fingerprints in Browser
|
||||
│ └── protection_service.py # Browser-Schutz
|
||||
└── external/
|
||||
├── proxy_service.py
|
||||
└── email_service.py
|
||||
```
|
||||
|
||||
### 4. Presentation Layer
|
||||
**UI und Controller**
|
||||
|
||||
```
|
||||
presentation/
|
||||
├── controllers/
|
||||
│ ├── account_controller.py
|
||||
│ └── fingerprint_controller.py
|
||||
└── views/
|
||||
├── account_view.py
|
||||
└── login_view.py
|
||||
```
|
||||
|
||||
## Fingerprint-System Design
|
||||
|
||||
### Fingerprint Entity (Kern-Domain)
|
||||
```python
|
||||
# domain/entities/browser_fingerprint.py
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
import uuid
|
||||
|
||||
@dataclass(frozen=True) # Immutable!
|
||||
class FingerprintId:
|
||||
value: str
|
||||
|
||||
@classmethod
|
||||
def generate(cls) -> 'FingerprintId':
|
||||
return cls(str(uuid.uuid4()))
|
||||
|
||||
@dataclass
|
||||
class BrowserFingerprint:
|
||||
"""Immutable Fingerprint Entity - Kern der Domain"""
|
||||
id: FingerprintId
|
||||
canvas_seed: int
|
||||
webgl_vendor: str
|
||||
webgl_renderer: str
|
||||
audio_context_params: dict
|
||||
navigator_properties: dict
|
||||
hardware_config: dict
|
||||
timezone: str
|
||||
fonts: list[str]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Serialisierung für Persistierung"""
|
||||
return {
|
||||
'id': self.id.value,
|
||||
'canvas_seed': self.canvas_seed,
|
||||
# ... weitere Felder
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'BrowserFingerprint':
|
||||
"""Deserialisierung aus Persistierung"""
|
||||
return cls(
|
||||
id=FingerprintId(data['id']),
|
||||
canvas_seed=data['canvas_seed'],
|
||||
# ... weitere Felder
|
||||
)
|
||||
```
|
||||
|
||||
### Fingerprint-Account-Session Verknüpfung
|
||||
```python
|
||||
# domain/entities/account.py
|
||||
@dataclass
|
||||
class Account:
|
||||
id: AccountId
|
||||
username: str
|
||||
platform: str
|
||||
fingerprint_id: FingerprintId # Verknüpfung!
|
||||
created_at: datetime
|
||||
|
||||
# domain/entities/browser_session.py
|
||||
@dataclass
|
||||
class BrowserSession:
|
||||
id: SessionId
|
||||
account_id: AccountId
|
||||
fingerprint_id: FingerprintId # Gleicher Fingerprint!
|
||||
cookies: str # Encrypted
|
||||
local_storage: str # Encrypted
|
||||
session_storage: str # Encrypted
|
||||
last_used: datetime
|
||||
is_valid: bool
|
||||
```
|
||||
|
||||
### Use Case: Login mit gespeichertem Fingerprint
|
||||
```python
|
||||
# application/use_cases/login_account/login_with_fingerprint_use_case.py
|
||||
class LoginWithFingerprintUseCase:
|
||||
def __init__(self,
|
||||
account_repo: IAccountRepository,
|
||||
fingerprint_repo: IFingerprintRepository,
|
||||
session_repo: ISessionRepository,
|
||||
browser_service: IBrowserService):
|
||||
self.account_repo = account_repo
|
||||
self.fingerprint_repo = fingerprint_repo
|
||||
self.session_repo = session_repo
|
||||
self.browser_service = browser_service
|
||||
|
||||
def execute(self, account_id: str) -> LoginResult:
|
||||
# 1. Account laden
|
||||
account = self.account_repo.find_by_id(AccountId(account_id))
|
||||
if not account:
|
||||
return LoginResult.failure("Account nicht gefunden")
|
||||
|
||||
# 2. Fingerprint laden
|
||||
fingerprint = self.fingerprint_repo.find_by_id(account.fingerprint_id)
|
||||
if not fingerprint:
|
||||
return LoginResult.failure("Fingerprint nicht gefunden")
|
||||
|
||||
# 3. Session laden
|
||||
session = self.session_repo.find_by_account_id(account.id)
|
||||
if not session or not session.is_valid:
|
||||
return LoginResult.failure("Keine gültige Session")
|
||||
|
||||
# 4. Browser mit Fingerprint starten
|
||||
browser = self.browser_service.create_with_fingerprint(fingerprint)
|
||||
|
||||
# 5. Session wiederherstellen
|
||||
browser.restore_session(session)
|
||||
|
||||
# 6. Login verifizieren
|
||||
if browser.verify_login(account.platform):
|
||||
return LoginResult.success(browser)
|
||||
else:
|
||||
return LoginResult.failure("Login fehlgeschlagen")
|
||||
```
|
||||
|
||||
### Repository Pattern (Clean!)
|
||||
```python
|
||||
# domain/repositories/fingerprint_repository.py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class IFingerprintRepository(ABC):
|
||||
@abstractmethod
|
||||
def save(self, fingerprint: BrowserFingerprint) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def find_by_id(self, id: FingerprintId) -> Optional[BrowserFingerprint]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def find_by_account_id(self, account_id: AccountId) -> Optional[BrowserFingerprint]:
|
||||
pass
|
||||
|
||||
# infrastructure/persistence/sqlite/sqlite_fingerprint_repository.py
|
||||
class SqliteFingerprintRepository(IFingerprintRepository):
|
||||
def save(self, fingerprint: BrowserFingerprint) -> None:
|
||||
# SQL Implementation
|
||||
query = "INSERT OR REPLACE INTO fingerprints ..."
|
||||
# Nur primitive Typen in DB!
|
||||
data = fingerprint.to_dict()
|
||||
self.db.execute(query, data)
|
||||
|
||||
def find_by_id(self, id: FingerprintId) -> Optional[BrowserFingerprint]:
|
||||
query = "SELECT * FROM fingerprints WHERE id = ?"
|
||||
row = self.db.fetchone(query, [id.value])
|
||||
return BrowserFingerprint.from_dict(row) if row else None
|
||||
```
|
||||
|
||||
### Dependency Injection Container
|
||||
```python
|
||||
# infrastructure/container.py
|
||||
class Container:
|
||||
def __init__(self):
|
||||
# Repositories
|
||||
self._fingerprint_repo = SqliteFingerprintRepository()
|
||||
self._account_repo = SqliteAccountRepository()
|
||||
self._session_repo = SqliteSessionRepository()
|
||||
|
||||
# Services
|
||||
self._browser_service = PlaywrightBrowserService()
|
||||
|
||||
# Use Cases
|
||||
self._login_use_case = LoginWithFingerprintUseCase(
|
||||
self._account_repo,
|
||||
self._fingerprint_repo,
|
||||
self._session_repo,
|
||||
self._browser_service
|
||||
)
|
||||
|
||||
@property
|
||||
def login_use_case(self) -> LoginWithFingerprintUseCase:
|
||||
return self._login_use_case
|
||||
```
|
||||
|
||||
## Datenbank-Schema
|
||||
|
||||
```sql
|
||||
-- Fingerprints Tabelle
|
||||
CREATE TABLE fingerprints (
|
||||
id TEXT PRIMARY KEY,
|
||||
canvas_seed INTEGER NOT NULL,
|
||||
webgl_vendor TEXT NOT NULL,
|
||||
webgl_renderer TEXT NOT NULL,
|
||||
audio_context_params TEXT NOT NULL, -- JSON
|
||||
navigator_properties TEXT NOT NULL, -- JSON
|
||||
hardware_config TEXT NOT NULL, -- JSON
|
||||
timezone TEXT NOT NULL,
|
||||
fonts TEXT NOT NULL, -- JSON Array
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Accounts Tabelle
|
||||
CREATE TABLE accounts (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
platform TEXT NOT NULL,
|
||||
fingerprint_id TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (fingerprint_id) REFERENCES fingerprints(id)
|
||||
);
|
||||
|
||||
-- Sessions Tabelle
|
||||
CREATE TABLE browser_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
account_id TEXT NOT NULL,
|
||||
fingerprint_id TEXT NOT NULL,
|
||||
cookies TEXT NOT NULL, -- Encrypted
|
||||
local_storage TEXT, -- Encrypted
|
||||
session_storage TEXT, -- Encrypted
|
||||
last_used TIMESTAMP,
|
||||
is_valid BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id),
|
||||
FOREIGN KEY (fingerprint_id) REFERENCES fingerprints(id)
|
||||
);
|
||||
|
||||
-- Index für Performance
|
||||
CREATE INDEX idx_accounts_fingerprint ON accounts(fingerprint_id);
|
||||
CREATE INDEX idx_sessions_account ON browser_sessions(account_id);
|
||||
```
|
||||
|
||||
## Vorteile dieser Architektur
|
||||
|
||||
1. **Testbarkeit**: Jede Schicht ist isoliert testbar
|
||||
2. **Flexibilität**: Repositories können ausgetauscht werden (SQLite → PostgreSQL)
|
||||
3. **Klarheit**: Klare Verantwortlichkeiten pro Schicht
|
||||
4. **Wartbarkeit**: Änderungen sind lokal begrenzt
|
||||
5. **Fingerprint-Konsistenz**: Ein Account = Ein Fingerprint = Konsistente Sessions
|
||||
|
||||
## Login-Flow mit Fingerprint
|
||||
|
||||
1. User wählt Account aus Liste
|
||||
2. System lädt Account mit verknüpftem Fingerprint
|
||||
3. Browser wird mit exakt diesem Fingerprint gestartet
|
||||
4. Gespeicherte Session (Cookies, Storage) wird geladen
|
||||
5. Browser navigiert zur Plattform
|
||||
6. Session ist wiederhergestellt = User ist eingeloggt
|
||||
|
||||
## Beispiel-Verwendung
|
||||
|
||||
```python
|
||||
# In der Presentation Layer
|
||||
container = Container()
|
||||
|
||||
# Login mit gespeichertem Fingerprint
|
||||
result = container.login_use_case.execute(account_id="abc-123")
|
||||
|
||||
if result.success:
|
||||
browser = result.browser
|
||||
# User ist jetzt eingeloggt mit dem gleichen Fingerprint
|
||||
else:
|
||||
print(f"Login fehlgeschlagen: {result.error}")
|
||||
```
|
||||
|
||||
Diese Architektur stellt sicher, dass:
|
||||
- Fingerprints konsistent bleiben
|
||||
- Sessions zuverlässig wiederhergestellt werden
|
||||
- Der Code wartbar und erweiterbar bleibt
|
||||
- Keine zirkulären Abhängigkeiten entstehen
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren