342 Zeilen
10 KiB
Markdown
342 Zeilen
10 KiB
Markdown
# 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 |