11 KiB
11 KiB
Clean Architecture Design - AccountForger
Status: Vorschlag/Entwurf. Diese Architektur ist im aktuellen Code nur teilweise umgesetzt und dient als Zielbild. Ordner‑/Dateinamen in Beispielen sind konzeptionell zu verstehen.
Ü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)
# 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
# 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
# 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!)
# 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
# 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
-- 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
- Testbarkeit: Jede Schicht ist isoliert testbar
- Flexibilität: Repositories können ausgetauscht werden (SQLite → PostgreSQL)
- Klarheit: Klare Verantwortlichkeiten pro Schicht
- Wartbarkeit: Änderungen sind lokal begrenzt
- Fingerprint-Konsistenz: Ein Account = Ein Fingerprint = Konsistente Sessions
Login-Flow mit Fingerprint
- User wählt Account aus Liste
- System lädt Account mit verknüpftem Fingerprint
- Browser wird mit exakt diesem Fingerprint gestartet
- Gespeicherte Session (Cookies, Storage) wird geladen
- Browser navigiert zur Plattform
- Session ist wiederhergestellt = User ist eingeloggt
Beispiel-Verwendung
# 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