Files
Claude Project Manager a25a26a01a Update changes
2026-01-18 18:15:34 +01:00

66 KiB

AccountForger Backend - Verifikationsserver Spezifikation

1. Executive Summary

AccountForger ist ein Desktop-Tool zur automatisierten Erstellung von Social-Media-Accounts (Instagram, Facebook, TikTok, X, etc.). Die Browser-Automation läuft lokal beim Kunden, aber die Verifikation (Email-Codes, SMS-Codes) wird über einen zentralen Server abgewickelt.

Architektur-Übersicht

┌─────────────────────────────────────────────────────────────────┐
│                         KUNDE                                    │
│  ┌─────────────────┐              ┌──────────────────┐          │
│  │  Desktop Client │              │  RUTX11 Router   │          │
│  │  (AccountForger)│              │  (SMS-Empfang)   │          │
│  └────────┬────────┘              └────────┬─────────┘          │
└───────────┼────────────────────────────────┼────────────────────┘
            │ HTTPS (API)                    │ HTTPS (Webhook)
            ▼                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                    VERIFIKATIONSSERVER (VPS)                     │
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │ Email       │  │ SMS         │  │ Client Registry        │  │
│  │ Service     │  │ Service     │  │ (API-Key → Nummern)    │  │
│  │ IMAP Polling│  │ Webhook     │  │                        │  │
│  └─────────────┘  └─────────────┘  └─────────────────────────┘  │
│                                                                  │
│  PostgreSQL                              FastAPI                 │
└─────────────────────────────────────────────────────────────────┘

Scope-Abgrenzung

In Scope (dieses Backend) Out of Scope
Email-Verifikations-API Admin-Panel/Dashboard
SMS-Verifikations-API Kundenverwaltung (CRUD)
RUTX11 Webhook-Empfang Router-Konfiguration
Phone-Rotation-Logik Abrechnung/Billing
API-Key-Validierung Lizenz-Management
Health-Check & Monitoring Desktop-Client

Wichtig: Clients, Router und Telefonnummern werden manuell oder über ein separates Admin-System in der Datenbank angelegt. Dieses Backend geht davon aus, dass diese Daten bereits existieren.


2. Client-Architektur (Kontext)

Der Desktop-Client folgt einer Clean Architecture mit Domain-Driven Design. Relevante Komponenten für die Backend-Integration:

2.1 Hauptkomponenten

AccountForger/
├── social_networks/
│   ├── base_automation.py          # Basis-Klasse für alle Plattformen
│   ├── instagram/
│   │   ├── instagram_automation.py # Hauptlogik
│   │   └── instagram_verification.py # Email-Code-Eingabe
│   ├── facebook/
│   │   └── facebook_verification.py
│   └── ... (tiktok, x, gmail, ok_ru, vk)
├── controllers/platform_controllers/
│   └── base_worker_thread.py       # QThread für Account-Erstellung
├── utils/
│   └── email_handler.py            # AKTUELL: IMAP-Polling (wird ersetzt)
└── browser/
    └── playwright_manager.py       # Browser-Steuerung

2.2 Aktuelle Email-Verifikation (zu ersetzen)

Der EmailHandler (utils/email_handler.py) macht aktuell direktes IMAP-Polling:

# AKTUELLE Implementierung (wird ersetzt)
class EmailHandler:
    def get_verification_code(self, target_email, platform, max_attempts=30):
        # Verbindet direkt zu IMAP-Server
        mail = imaplib.IMAP4_SSL(self.config["imap_server"])
        mail.login(self.config["imap_user"], self.config["imap_pass"])
        # Sucht nach Emails, extrahiert Code per Regex
        ...

Problem: IMAP-Credentials liegen im Client.

Lösung: Client fragt Server-API, Server macht IMAP-Polling.

2.3 Aktuelle SMS-Verifikation (nicht implementiert)

SMS-Verifikation ist ein Placeholder:

# social_networks/facebook/facebook_verification.py
def handle_sms_verification(self, phone_number: str, timeout: int = 120):
    logger.warning("SMS-Verifikation noch nicht implementiert")
    return None

3. Backend API-Spezifikation

3.1 OpenAPI-Endpunkte

openapi: 3.0.3
info:
  title: AccountForger Verification API
  version: 1.0.0
  description: Backend für Email- und SMS-Verifikation

servers:
  - url: https://verify.example.com/api/v1

security:
  - ApiKeyAuth: []

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Client-spezifischer API-Key

  schemas:
    EmailRequest:
      type: object
      required:
        - platform
      properties:
        platform:
          type: string
          enum: [instagram, facebook, tiktok, x, gmail, twitter, vk, ok_ru]
          description: Zielplattform für die Registrierung
        preferred_domain:
          type: string
          description: Bevorzugte Email-Domain (optional)

    EmailResponse:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
        email_address:
          type: string
          format: email
        expires_at:
          type: string
          format: date-time

    SMSRequest:
      type: object
      required:
        - platform
      properties:
        platform:
          type: string
          enum: [instagram, facebook, tiktok, x, gmail, twitter, vk, ok_ru]

    SMSResponse:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
        phone_number:
          type: string
          description: Telefonnummer im Format +49...
        expires_at:
          type: string
          format: date-time

    CodeResponse:
      type: object
      properties:
        status:
          type: string
          enum: [pending, received, expired, failed]
        code:
          type: string
          nullable: true
          description: Verifikationscode (null wenn noch nicht empfangen)
        received_at:
          type: string
          format: date-time
          nullable: true

    WebhookPayload:
      type: object
      required:
        - sender
        - message
      properties:
        sender:
          type: string
          description: Absender-Nummer
        message:
          type: string
          description: SMS-Inhalt
        timestamp:
          type: string
          format: date-time
        sim_slot:
          type: integer
          enum: [1, 2]

    PhoneAvailability:
      type: object
      properties:
        available_count:
          type: integer
        phones:
          type: array
          items:
            type: object
            properties:
              phone_number:
                type: string
              cooldown_until:
                type: string
                format: date-time
                nullable: true
              last_platform:
                type: string
                nullable: true

    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        retry_after:
          type: integer
          description: Sekunden bis Retry (bei Rate-Limit)

paths:
  /verification/email/request:
    post:
      summary: Email-Adresse für Verifikation anfordern
      operationId: requestEmail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
      responses:
        '201':
          description: Email-Adresse zugewiesen
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailResponse'
        '401':
          description: Ungültiger API-Key
        '429':
          description: Rate-Limit erreicht
          headers:
            Retry-After:
              schema:
                type: integer
        '503':
          description: Keine Email-Adresse verfügbar

  /verification/email/code/{request_id}:
    get:
      summary: Email-Verifikationscode abfragen (Polling)
      operationId: getEmailCode
      parameters:
        - name: request_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Status des Verifikationscodes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeResponse'
        '404':
          description: Request nicht gefunden
        '408':
          description: Timeout - kein Code empfangen

  /verification/sms/request:
    post:
      summary: Telefonnummer für SMS-Verifikation anfordern
      operationId: requestSMS
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SMSRequest'
      responses:
        '201':
          description: Telefonnummer zugewiesen
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SMSResponse'
        '401':
          description: Ungültiger API-Key
        '429':
          description: Rate-Limit erreicht
        '503':
          description: Keine Telefonnummer verfügbar

  /verification/sms/code/{request_id}:
    get:
      summary: SMS-Verifikationscode abfragen (Polling)
      operationId: getSMSCode
      parameters:
        - name: request_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Status des Verifikationscodes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeResponse'
        '404':
          description: Request nicht gefunden
        '408':
          description: Timeout - kein Code empfangen

  /webhook/rutx11/sms:
    post:
      summary: SMS-Webhook vom RUTX11 Router
      operationId: receiveSMS
      security:
        - RouterToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookPayload'
      responses:
        '200':
          description: SMS empfangen und verarbeitet
        '401':
          description: Ungültiger Router-Token
        '422':
          description: Ungültiges Payload-Format

  /phone/available:
    get:
      summary: Verfügbare Telefonnummern abfragen
      operationId: getAvailablePhones
      responses:
        '200':
          description: Liste verfügbarer Nummern
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PhoneAvailability'

  /health:
    get:
      summary: Health-Check
      operationId: healthCheck
      security: []
      responses:
        '200':
          description: Service ist gesund
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [healthy, degraded]
                  database:
                    type: string
                  redis:
                    type: string
                  imap_connections:
                    type: integer

3.2 PostgreSQL-Schema

-- =====================================================
-- EXTENSION für UUID
-- =====================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- =====================================================
-- CLIENTS (Lizenznehmer)
-- Werden MANUELL oder über separates Admin-System angelegt
-- =====================================================
CREATE TABLE clients (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    license_key VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    api_key_hash VARCHAR(255) NOT NULL,  -- bcrypt hash
    tier VARCHAR(20) DEFAULT 'standard' CHECK (tier IN ('standard', 'premium', 'enterprise')),
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_clients_api_key_hash ON clients(api_key_hash);
CREATE INDEX idx_clients_license_key ON clients(license_key);

-- =====================================================
-- TIER LIMITS (Rate-Limiting pro Tier)
-- =====================================================
CREATE TABLE tier_limits (
    tier VARCHAR(20) PRIMARY KEY,
    email_requests_per_hour INTEGER NOT NULL,
    sms_requests_per_hour INTEGER NOT NULL,
    max_concurrent_verifications INTEGER NOT NULL
);

INSERT INTO tier_limits VALUES
    ('standard', 50, 20, 5),
    ('premium', 200, 100, 20),
    ('enterprise', 1000, 500, 100);

-- =====================================================
-- ROUTERS (RUTX11 beim Kunden)
-- Werden MANUELL angelegt nach Router-Versand
-- =====================================================
CREATE TABLE routers (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
    router_token VARCHAR(100) UNIQUE NOT NULL,  -- Für Webhook-Auth
    model VARCHAR(50) DEFAULT 'RUTX11',
    serial_number VARCHAR(100),
    webhook_url VARCHAR(500),  -- Für Health-Checks
    is_online BOOLEAN DEFAULT false,
    last_seen_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_routers_client_id ON routers(client_id);
CREATE INDEX idx_routers_token ON routers(router_token);

-- =====================================================
-- PHONE NUMBERS (eSIMs in den Routern)
-- Werden MANUELL angelegt nach eSIM-Aktivierung
-- =====================================================
CREATE TABLE phone_numbers (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    router_id UUID NOT NULL REFERENCES routers(id) ON DELETE CASCADE,
    phone_number VARCHAR(20) UNIQUE NOT NULL,  -- Format: +49...
    esim_slot INTEGER NOT NULL CHECK (esim_slot IN (1, 2)),
    carrier VARCHAR(100),  -- z.B. "Telekom", "Vodafone"
    is_active BOOLEAN DEFAULT true,
    cooldown_until TIMESTAMP WITH TIME ZONE,  -- Gesperrt bis
    usage_count INTEGER DEFAULT 0,
    last_used_at TIMESTAMP WITH TIME ZONE,
    last_platform VARCHAR(50),  -- Letzte Plattform
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    UNIQUE(router_id, esim_slot)
);

CREATE INDEX idx_phone_numbers_router_id ON phone_numbers(router_id);
CREATE INDEX idx_phone_numbers_cooldown ON phone_numbers(cooldown_until) WHERE is_active = true;

-- =====================================================
-- EMAIL ACCOUNTS (Catch-All Domains für Server)
-- Werden MANUELL angelegt
-- =====================================================
CREATE TABLE email_accounts (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    email_address VARCHAR(255) UNIQUE NOT NULL,  -- z.B. info@domain.com
    imap_server VARCHAR(255) NOT NULL,
    imap_port INTEGER DEFAULT 993,
    password_encrypted BYTEA NOT NULL,  -- AES-256 verschlüsselt
    domain VARCHAR(255) NOT NULL,  -- Catch-All Domain
    is_active BOOLEAN DEFAULT true,
    last_checked_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_email_accounts_domain ON email_accounts(domain) WHERE is_active = true;

-- =====================================================
-- VERIFICATION REQUESTS (Kernentität)
-- =====================================================
CREATE TYPE verification_type AS ENUM ('email', 'sms');
CREATE TYPE verification_status AS ENUM ('pending', 'polling', 'received', 'expired', 'failed');

CREATE TABLE verification_requests (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    client_id UUID NOT NULL REFERENCES clients(id),

    -- Request-Details
    platform VARCHAR(50) NOT NULL,
    type verification_type NOT NULL,

    -- Email-spezifisch
    email_address VARCHAR(255),
    email_account_id UUID REFERENCES email_accounts(id),

    -- SMS-spezifisch
    phone_number_id UUID REFERENCES phone_numbers(id),

    -- Status
    status verification_status DEFAULT 'pending',
    verification_code VARCHAR(20),

    -- Timestamps
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    code_received_at TIMESTAMP WITH TIME ZONE,

    -- Constraints
    CHECK (
        (type = 'email' AND email_address IS NOT NULL) OR
        (type = 'sms' AND phone_number_id IS NOT NULL)
    )
);

CREATE INDEX idx_verification_requests_client ON verification_requests(client_id);
CREATE INDEX idx_verification_requests_status ON verification_requests(status) WHERE status = 'pending';
CREATE INDEX idx_verification_requests_email ON verification_requests(email_address) WHERE type = 'email';
CREATE INDEX idx_verification_requests_phone ON verification_requests(phone_number_id) WHERE type = 'sms';
CREATE INDEX idx_verification_requests_expires ON verification_requests(expires_at) WHERE status IN ('pending', 'polling');

-- =====================================================
-- SMS MESSAGES (Empfangene SMS vom Router)
-- =====================================================
CREATE TABLE sms_messages (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    router_id UUID NOT NULL REFERENCES routers(id),
    phone_number VARCHAR(20) NOT NULL,  -- Empfänger-Nummer
    sender VARCHAR(50) NOT NULL,  -- Absender (z.B. "Instagram")
    message_body TEXT NOT NULL,
    sim_slot INTEGER,

    -- Matching
    processed BOOLEAN DEFAULT false,
    matched_request_id UUID REFERENCES verification_requests(id),
    extracted_code VARCHAR(20),

    -- Timestamps
    received_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    processed_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX idx_sms_messages_router ON sms_messages(router_id);
CREATE INDEX idx_sms_messages_unprocessed ON sms_messages(received_at) WHERE processed = false;
CREATE INDEX idx_sms_messages_phone ON sms_messages(phone_number, received_at DESC);

-- =====================================================
-- RATE LIMIT TRACKING
-- =====================================================
CREATE TABLE rate_limit_tracking (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    client_id UUID NOT NULL REFERENCES clients(id),
    request_type VARCHAR(20) NOT NULL,  -- 'email' oder 'sms'
    window_start TIMESTAMP WITH TIME ZONE NOT NULL,
    request_count INTEGER DEFAULT 1,

    UNIQUE(client_id, request_type, window_start)
);

CREATE INDEX idx_rate_limit_client ON rate_limit_tracking(client_id, request_type, window_start DESC);

-- =====================================================
-- AUDIT LOG (Optional, für Debugging)
-- =====================================================
CREATE TABLE audit_log (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    client_id UUID REFERENCES clients(id),
    action VARCHAR(100) NOT NULL,
    details JSONB,
    ip_address INET,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_audit_log_client ON audit_log(client_id, created_at DESC);

3.3 Entity-Relationship-Diagramm

erDiagram
    CLIENTS ||--o{ ROUTERS : "besitzt"
    CLIENTS ||--o{ VERIFICATION_REQUESTS : "erstellt"
    CLIENTS }o--|| TIER_LIMITS : "hat"

    ROUTERS ||--o{ PHONE_NUMBERS : "enthält"
    ROUTERS ||--o{ SMS_MESSAGES : "empfängt"

    PHONE_NUMBERS ||--o{ VERIFICATION_REQUESTS : "wird_verwendet_für"

    EMAIL_ACCOUNTS ||--o{ VERIFICATION_REQUESTS : "wird_verwendet_für"

    VERIFICATION_REQUESTS ||--o| SMS_MESSAGES : "wird_gematcht_mit"

    CLIENTS {
        uuid id PK
        string license_key UK
        string api_key_hash
        string tier
        boolean is_active
    }

    ROUTERS {
        uuid id PK
        uuid client_id FK
        string router_token UK
        boolean is_online
    }

    PHONE_NUMBERS {
        uuid id PK
        uuid router_id FK
        string phone_number UK
        int esim_slot
        timestamp cooldown_until
    }

    EMAIL_ACCOUNTS {
        uuid id PK
        string email_address UK
        string imap_server
        bytea password_encrypted
        string domain
    }

    VERIFICATION_REQUESTS {
        uuid id PK
        uuid client_id FK
        string platform
        enum type
        string email_address
        uuid phone_number_id FK
        enum status
        string verification_code
    }

    SMS_MESSAGES {
        uuid id PK
        uuid router_id FK
        string sender
        string message_body
        uuid matched_request_id FK
    }

4. Services - Implementierungsdetails

4.1 EmailService

Zweck: IMAP-Polling nach Verifikations-Emails und Code-Extraktion.

# app/services/email_service.py

import asyncio
import aioimaplib
import re
from typing import Optional
from datetime import datetime, timedelta
from email import message_from_bytes
from email.header import decode_header

class EmailService:
    """Service für IMAP-Polling und Email-Code-Extraktion."""

    # Plattform-spezifische Regex-Patterns für Code-Extraktion
    CODE_PATTERNS = {
        "instagram": [
            r"(\d{6}) ist dein Instagram-Code",
            r"(\d{6}) is your Instagram code",
            r"Dein Code ist (\d{6})",
            r"Your code is (\d{6})",
            r"\b(\d{6})\b"  # Fallback: 6-stellige Zahl
        ],
        "facebook": [
            r"FB-(\d{5})",
            r"Bestätigungscode lautet (\d{5})",
            r"\b(\d{5})\b"  # Fallback: 5-stellige Zahl
        ],
        "tiktok": [
            r"(\d{6}) ist dein Bestätigungscode",
            r"(\d{6}) is your confirmation code",
            r"\b(\d{6})\b"
        ],
        "x": [
            r"(\d{6}) ist dein X Verifizierungscode",
            r"(\d{6}) is your X verification code",
            r"\b(\d{6})\b"
        ],
        "default": [
            r"[Cc]ode[:\s]*(\d{4,8})",
            r"\b(\d{6})\b"
        ]
    }

    # Betreff-Keywords pro Plattform
    SUBJECT_KEYWORDS = {
        "instagram": ["instagram", "bestätigungscode", "verification code"],
        "facebook": ["facebook", "fb-", "bestätigungscode"],
        "tiktok": ["tiktok", "bestätigungscode", "confirmation"],
        "x": ["verification code", "verifizierungscode"],
        "default": ["code", "verification", "bestätigung"]
    }

    def __init__(self, db_session, encryption_service):
        self.db = db_session
        self.encryption = encryption_service
        self._imap_connections = {}

    async def poll_for_code(
        self,
        request_id: str,
        email_address: str,
        platform: str,
        timeout_seconds: int = 120
    ) -> Optional[str]:
        """
        Pollt IMAP-Server nach Verifikations-Email.

        Args:
            request_id: ID der Verifikationsanfrage
            email_address: Ziel-Email-Adresse
            platform: Plattform (instagram, facebook, etc.)
            timeout_seconds: Maximale Wartezeit

        Returns:
            Verifikationscode oder None
        """
        # Email-Account für Domain ermitteln
        domain = email_address.split("@")[1]
        email_account = await self._get_email_account_for_domain(domain)

        if not email_account:
            raise ValueError(f"Kein Email-Account für Domain {domain} konfiguriert")

        # IMAP-Verbindung herstellen/wiederverwenden
        imap = await self._get_imap_connection(email_account)

        start_time = datetime.utcnow()
        poll_interval = 2  # Sekunden

        while (datetime.utcnow() - start_time).seconds < timeout_seconds:
            # Nach neuen Emails suchen
            code = await self._search_and_extract_code(
                imap,
                email_address,
                platform,
                since=start_time - timedelta(minutes=5)
            )

            if code:
                # Request aktualisieren
                await self._update_request_with_code(request_id, code)
                return code

            await asyncio.sleep(poll_interval)

        # Timeout
        await self._mark_request_expired(request_id)
        return None

    async def _search_and_extract_code(
        self,
        imap: aioimaplib.IMAP4_SSL,
        target_email: str,
        platform: str,
        since: datetime
    ) -> Optional[str]:
        """Durchsucht Emails nach Verifikationscode."""

        # INBOX auswählen
        await imap.select("INBOX")

        # Suche nach Emails seit timestamp
        date_str = since.strftime("%d-%b-%Y")
        _, data = await imap.search(f'(SINCE "{date_str}")')

        email_ids = data[0].split()

        # Neueste zuerst
        for email_id in reversed(email_ids[-20:]):  # Max 20 Emails prüfen
            _, msg_data = await imap.fetch(email_id, "(RFC822)")

            if not msg_data or not msg_data[1]:
                continue

            msg = message_from_bytes(msg_data[1])

            # Empfänger prüfen
            to_addr = self._extract_email_from_header(msg.get("To", ""))
            if to_addr.lower() != target_email.lower():
                continue

            # Betreff prüfen
            subject = self._decode_header(msg.get("Subject", ""))
            if not self._is_relevant_subject(subject, platform):
                continue

            # Body extrahieren und Code suchen
            body = self._extract_body(msg)
            code = self._extract_code(body, platform)

            if code:
                return code

        return None

    def _extract_code(self, text: str, platform: str) -> Optional[str]:
        """Extrahiert Verifikationscode aus Text."""
        patterns = self.CODE_PATTERNS.get(platform, []) + self.CODE_PATTERNS["default"]

        for pattern in patterns:
            match = re.search(pattern, text, re.IGNORECASE)
            if match:
                return match.group(1)

        return None

    def _is_relevant_subject(self, subject: str, platform: str) -> bool:
        """Prüft ob Betreff zur Plattform passt."""
        keywords = self.SUBJECT_KEYWORDS.get(platform, []) + self.SUBJECT_KEYWORDS["default"]
        subject_lower = subject.lower()
        return any(kw in subject_lower for kw in keywords)

4.2 SMSService

Zweck: Verarbeitung eingehender SMS via Webhook und Matching mit offenen Requests.

# app/services/sms_service.py

import re
from typing import Optional
from datetime import datetime

class SMSService:
    """Service für SMS-Verarbeitung und Request-Matching."""

    # Code-Patterns für SMS (kürzer als Email-Patterns)
    SMS_CODE_PATTERNS = {
        "instagram": [r"(\d{6})"],
        "facebook": [r"(\d{5})", r"FB-(\d{5})"],
        "tiktok": [r"(\d{6})"],
        "x": [r"(\d{6})"],
        "default": [r"(\d{4,8})"]
    }

    # Absender-Keywords pro Plattform
    SENDER_KEYWORDS = {
        "instagram": ["instagram", "32665"],
        "facebook": ["facebook", "32665", "fb"],
        "tiktok": ["tiktok"],
        "x": ["twitter", "x.com"],
    }

    def __init__(self, db_session):
        self.db = db_session

    async def process_incoming_sms(
        self,
        router_token: str,
        sender: str,
        message: str,
        phone_number: str,
        sim_slot: Optional[int] = None
    ) -> dict:
        """
        Verarbeitet eingehende SMS vom Router-Webhook.

        Args:
            router_token: Token zur Router-Authentifizierung
            sender: Absender der SMS
            message: SMS-Inhalt
            phone_number: Empfänger-Nummer (eSIM)
            sim_slot: SIM-Slot (1 oder 2)

        Returns:
            dict mit Verarbeitungsergebnis
        """
        # Router validieren
        router = await self._validate_router_token(router_token)
        if not router:
            raise PermissionError("Ungültiger Router-Token")

        # Router als online markieren
        await self._update_router_last_seen(router.id)

        # SMS in DB speichern
        sms_record = await self._store_sms(
            router_id=router.id,
            phone_number=phone_number,
            sender=sender,
            message=message,
            sim_slot=sim_slot
        )

        # Offenen Request finden
        request = await self._find_matching_request(phone_number)

        if not request:
            # Kein offener Request - SMS trotzdem speichern
            return {
                "status": "stored",
                "message": "SMS gespeichert, kein offener Request",
                "sms_id": str(sms_record.id)
            }

        # Code extrahieren
        code = self._extract_code_from_sms(message, request.platform)

        if code:
            # Request aktualisieren
            await self._update_request_with_code(request.id, code, sms_record.id)

            return {
                "status": "matched",
                "request_id": str(request.id),
                "code": code
            }

        return {
            "status": "stored",
            "message": "SMS gespeichert, kein Code extrahiert"
        }

    async def _find_matching_request(self, phone_number: str):
        """
        Findet offenen Request für Telefonnummer.

        Matching-Logik:
        1. Status = 'pending' oder 'polling'
        2. phone_number stimmt überein
        3. Noch nicht abgelaufen
        4. Ältester Request zuerst (FIFO)
        """
        query = """
            SELECT vr.* FROM verification_requests vr
            JOIN phone_numbers pn ON vr.phone_number_id = pn.id
            WHERE pn.phone_number = $1
              AND vr.status IN ('pending', 'polling')
              AND vr.expires_at > NOW()
              AND vr.type = 'sms'
            ORDER BY vr.created_at ASC
            LIMIT 1
        """
        return await self.db.fetch_one(query, phone_number)

    def _extract_code_from_sms(self, message: str, platform: str) -> Optional[str]:
        """Extrahiert Code aus SMS basierend auf Plattform."""
        patterns = self.SMS_CODE_PATTERNS.get(platform, []) + self.SMS_CODE_PATTERNS["default"]

        for pattern in patterns:
            match = re.search(pattern, message)
            if match:
                return match.group(1)

        return None

4.3 PhoneRotationService

Zweck: Intelligente Auswahl von Telefonnummern mit Cooldown und Platform-Awareness.

# app/services/phone_rotation_service.py

from datetime import datetime, timedelta
from typing import Optional, List
import random

class PhoneRotationService:
    """Service für intelligente Telefonnummern-Rotation."""

    # Cooldown-Zeiten pro Plattform (in Minuten)
    PLATFORM_COOLDOWNS = {
        "instagram": 60,   # 1 Stunde
        "facebook": 45,
        "tiktok": 30,
        "x": 30,
        "default": 30
    }

    def __init__(self, db_session):
        self.db = db_session

    async def get_available_phone(
        self,
        client_id: str,
        platform: str
    ) -> Optional[dict]:
        """
        Wählt optimale Telefonnummer für Client und Plattform.

        Auswahlkriterien (Priorität):
        1. Nummer gehört zum Client (über Router)
        2. Nummer ist aktiv
        3. Cooldown abgelaufen
        4. Wurde nicht kürzlich für dieselbe Plattform verwendet
        5. Geringste Gesamtnutzung (Load-Balancing)

        Returns:
            dict mit phone_number und id, oder None
        """
        query = """
            SELECT
                pn.id,
                pn.phone_number,
                pn.usage_count,
                pn.last_platform,
                pn.last_used_at,
                pn.cooldown_until
            FROM phone_numbers pn
            JOIN routers r ON pn.router_id = r.id
            WHERE r.client_id = $1
              AND pn.is_active = true
              AND r.is_online = true
              AND (pn.cooldown_until IS NULL OR pn.cooldown_until < NOW())
            ORDER BY
                -- Bevorzuge Nummern die nicht für diese Plattform verwendet wurden
                CASE WHEN pn.last_platform = $2 THEN 1 ELSE 0 END ASC,
                -- Dann nach Nutzungscount (Load-Balancing)
                pn.usage_count ASC,
                -- Dann zufällig für Variation
                RANDOM()
            LIMIT 1
        """

        phone = await self.db.fetch_one(query, client_id, platform)

        if phone:
            return {
                "id": str(phone["id"]),
                "phone_number": phone["phone_number"]
            }

        return None

    async def mark_phone_used(
        self,
        phone_id: str,
        platform: str
    ) -> None:
        """
        Markiert Nummer als verwendet und setzt Cooldown.
        """
        cooldown_minutes = self.PLATFORM_COOLDOWNS.get(platform, 30)
        cooldown_until = datetime.utcnow() + timedelta(minutes=cooldown_minutes)

        query = """
            UPDATE phone_numbers
            SET
                usage_count = usage_count + 1,
                last_used_at = NOW(),
                last_platform = $2,
                cooldown_until = $3
            WHERE id = $1
        """

        await self.db.execute(query, phone_id, platform, cooldown_until)

    async def get_available_count(self, client_id: str) -> int:
        """Gibt Anzahl verfügbarer Nummern zurück."""
        query = """
            SELECT COUNT(*) FROM phone_numbers pn
            JOIN routers r ON pn.router_id = r.id
            WHERE r.client_id = $1
              AND pn.is_active = true
              AND r.is_online = true
              AND (pn.cooldown_until IS NULL OR pn.cooldown_until < NOW())
        """
        result = await self.db.fetch_one(query, client_id)
        return result["count"]

    async def release_phone(self, phone_id: str) -> None:
        """Gibt Nummer vorzeitig frei (z.B. bei Fehler)."""
        query = """
            UPDATE phone_numbers
            SET cooldown_until = NULL
            WHERE id = $1
        """
        await self.db.execute(query, phone_id)

4.4 AuthService

Zweck: API-Key-Validierung und Rate-Limiting.

# app/services/auth_service.py

import bcrypt
from datetime import datetime, timedelta
from typing import Optional
from fastapi import HTTPException, Header

class AuthService:
    """Service für Authentifizierung und Rate-Limiting."""

    def __init__(self, db_session):
        self.db = db_session

    async def validate_api_key(self, api_key: str) -> dict:
        """
        Validiert API-Key und gibt Client-Info zurück.

        Returns:
            dict mit client_id, tier, etc.

        Raises:
            HTTPException 401 bei ungültigem Key
        """
        if not api_key:
            raise HTTPException(status_code=401, detail="API-Key fehlt")

        # Alle aktiven Clients laden
        query = """
            SELECT id, license_key, api_key_hash, tier, name
            FROM clients
            WHERE is_active = true
        """
        clients = await self.db.fetch_all(query)

        # Key gegen alle Hashes prüfen
        for client in clients:
            if bcrypt.checkpw(api_key.encode(), client["api_key_hash"].encode()):
                return {
                    "client_id": str(client["id"]),
                    "license_key": client["license_key"],
                    "tier": client["tier"],
                    "name": client["name"]
                }

        raise HTTPException(status_code=401, detail="Ungültiger API-Key")

    async def check_rate_limit(
        self,
        client_id: str,
        tier: str,
        request_type: str  # 'email' oder 'sms'
    ) -> bool:
        """
        Prüft und aktualisiert Rate-Limit.

        Returns:
            True wenn Request erlaubt, sonst HTTPException 429
        """
        # Tier-Limits laden
        limit_query = """
            SELECT email_requests_per_hour, sms_requests_per_hour
            FROM tier_limits
            WHERE tier = $1
        """
        limits = await self.db.fetch_one(limit_query, tier)

        if not limits:
            limits = {"email_requests_per_hour": 50, "sms_requests_per_hour": 20}

        max_requests = (
            limits["email_requests_per_hour"]
            if request_type == "email"
            else limits["sms_requests_per_hour"]
        )

        # Aktuelle Stunde
        window_start = datetime.utcnow().replace(minute=0, second=0, microsecond=0)

        # Anzahl Requests in dieser Stunde
        count_query = """
            SELECT COALESCE(SUM(request_count), 0) as total
            FROM rate_limit_tracking
            WHERE client_id = $1
              AND request_type = $2
              AND window_start >= $3
        """
        result = await self.db.fetch_one(count_query, client_id, request_type, window_start)
        current_count = result["total"]

        if current_count >= max_requests:
            # Rate-Limit erreicht
            retry_after = 3600 - (datetime.utcnow() - window_start).seconds
            raise HTTPException(
                status_code=429,
                detail="Rate-Limit erreicht",
                headers={"Retry-After": str(retry_after)}
            )

        # Request zählen
        upsert_query = """
            INSERT INTO rate_limit_tracking (client_id, request_type, window_start, request_count)
            VALUES ($1, $2, $3, 1)
            ON CONFLICT (client_id, request_type, window_start)
            DO UPDATE SET request_count = rate_limit_tracking.request_count + 1
        """
        await self.db.execute(upsert_query, client_id, request_type, window_start)

        return True

# FastAPI Dependency
async def get_current_client(
    x_api_key: str = Header(..., alias="X-API-Key"),
    db = Depends(get_db)
) -> dict:
    """FastAPI Dependency für API-Key-Validierung."""
    auth_service = AuthService(db)
    return await auth_service.validate_api_key(x_api_key)

5. Sequenzdiagramme

5.1 Email-Verifikations-Flow

sequenceDiagram
    participant C as Desktop Client
    participant S as Verification Server
    participant DB as PostgreSQL
    participant IMAP as IMAP Server
    participant P as Platform (Instagram)

    C->>S: POST /verification/email/request<br/>{platform: "instagram"}
    S->>S: API-Key validieren
    S->>DB: Rate-Limit prüfen
    S->>DB: Freie Email-Adresse wählen
    S->>DB: Verification Request anlegen
    S-->>C: 201 {request_id, email_address}

    Note over C: Client registriert Account<br/>mit der Email-Adresse

    C->>P: Account-Registrierung mit Email
    P->>IMAP: Verifikations-Email senden

    loop Polling (max 120s)
        C->>S: GET /verification/email/code/{request_id}
        S->>IMAP: Emails abrufen (IMAP)

        alt Email gefunden
            S->>S: Code extrahieren (Regex)
            S->>DB: Code speichern
            S-->>C: 200 {status: "received", code: "123456"}
        else Email nicht gefunden
            S-->>C: 200 {status: "pending", code: null}
        end
    end

    Note over C: Client gibt Code ein
    C->>P: Verifikationscode eingeben
    P-->>C: Account verifiziert

5.2 SMS-Verifikations-Flow

sequenceDiagram
    participant C as Desktop Client
    participant S as Verification Server
    participant DB as PostgreSQL
    participant R as RUTX11 Router
    participant P as Platform (Instagram)

    C->>S: POST /verification/sms/request<br/>{platform: "instagram"}
    S->>S: API-Key validieren
    S->>DB: Rate-Limit prüfen
    S->>DB: PhoneRotation: Freie Nummer wählen
    S->>DB: Verification Request anlegen
    S->>DB: Cooldown setzen
    S-->>C: 201 {request_id, phone_number: "+49..."}

    Note over C: Client registriert Account<br/>mit der Telefonnummer

    C->>P: Account-Registrierung mit Telefon
    P->>R: SMS senden an +49...

    Note over R: Router empfängt SMS
    R->>S: POST /webhook/rutx11/sms<br/>{sender, message, sim_slot}
    S->>S: Router-Token validieren
    S->>DB: SMS speichern
    S->>DB: Offenen Request finden (phone_number match)
    S->>S: Code extrahieren (Regex)
    S->>DB: Request mit Code aktualisieren
    S-->>R: 200 OK

    loop Polling (parallel)
        C->>S: GET /verification/sms/code/{request_id}
        alt Code empfangen
            S->>DB: Request Status abrufen
            S-->>C: 200 {status: "received", code: "123456"}
        else Noch kein Code
            S-->>C: 200 {status: "pending", code: null}
        end
    end

    Note over C: Client gibt Code ein
    C->>P: Verifikationscode eingeben
    P-->>C: Account verifiziert

5.3 Webhook-Processing

sequenceDiagram
    participant R as RUTX11 Router
    participant S as Verification Server
    participant DB as PostgreSQL

    R->>S: POST /webhook/rutx11/sms
    Note over R,S: Header: X-Router-Token: ABC123
    Note over R,S: Body: {sender, message, timestamp, sim_slot}

    S->>DB: SELECT * FROM routers<br/>WHERE router_token = 'ABC123'

    alt Router nicht gefunden
        S-->>R: 401 Unauthorized
    else Router gefunden
        S->>DB: UPDATE routers SET<br/>last_seen_at = NOW(),<br/>is_online = true

        S->>DB: INSERT INTO sms_messages<br/>(router_id, sender, message...)

        S->>DB: SELECT * FROM verification_requests<br/>WHERE phone_number = ...<br/>AND status = 'pending'<br/>AND expires_at > NOW()

        alt Request gefunden
            S->>S: Code extrahieren
            S->>DB: UPDATE verification_requests<br/>SET code = '123456',<br/>status = 'received'
            S->>DB: UPDATE sms_messages<br/>SET matched_request_id = ...
            S-->>R: 200 {status: "matched"}
        else Kein Request
            S-->>R: 200 {status: "stored"}
        end
    end

6. Fehlerszenarien & Edge Cases

6.1 Error Responses

Szenario HTTP Status Response Body Client-Aktion
Ungültiger API-Key 401 {"error": "unauthorized", "message": "Ungültiger API-Key"} Lizenz prüfen
Rate-Limit erreicht 429 {"error": "rate_limit", "message": "...", "retry_after": 1800} Warten und Retry
Keine Email verfügbar 503 {"error": "no_resource", "message": "Keine Email-Adresse verfügbar"} Später versuchen
Keine Nummer verfügbar 503 {"error": "no_resource", "message": "Keine Telefonnummer verfügbar"} Später versuchen
Request nicht gefunden 404 {"error": "not_found", "message": "Request nicht gefunden"} Neuen Request starten
Code-Timeout 408 {"error": "timeout", "message": "Kein Code innerhalb Timeout empfangen"} Retry oder manuell
Router offline 503 {"error": "router_offline", "message": "Router nicht erreichbar"} Support kontaktieren
Ungültiger Router-Token 401 {"error": "unauthorized", "message": "Ungültiger Router-Token"} Router-Config prüfen

6.2 Timeout-Behandlung

# Email-Verifikation
DEFAULT_EMAIL_TIMEOUT = 120  # Sekunden
MAX_EMAIL_TIMEOUT = 300

# SMS-Verifikation
DEFAULT_SMS_TIMEOUT = 180  # SMS kann länger dauern
MAX_SMS_TIMEOUT = 300

# Request-Expiration
REQUEST_EXPIRY_BUFFER = 60  # Request läuft 60s nach Timeout ab

6.3 Retry-Logik (Client-seitig)

# Empfohlene Polling-Intervalle
INITIAL_POLL_INTERVAL = 2  # Sekunden
MAX_POLL_INTERVAL = 5
BACKOFF_FACTOR = 1.5

# Empfohlene Retry-Strategie bei Fehlern
MAX_RETRIES = 3
RETRY_DELAYS = [5, 15, 30]  # Sekunden

7. Integration mit Desktop-Client

7.1 VerificationClient-Klasse (NEU)

Diese Datei muss im Client erstellt werden: utils/verification_client.py

# utils/verification_client.py
"""
Client für die Kommunikation mit dem Verifikationsserver.
Ersetzt direktes IMAP-Polling durch API-Calls.
"""

import requests
import time
import logging
from typing import Optional, Dict, Any
from dataclasses import dataclass
from enum import Enum

logger = logging.getLogger("verification_client")


class VerificationStatus(Enum):
    PENDING = "pending"
    POLLING = "polling"
    RECEIVED = "received"
    EXPIRED = "expired"
    FAILED = "failed"


@dataclass
class VerificationResponse:
    """Response von einer Verifikationsanfrage."""
    request_id: str
    email_address: Optional[str] = None
    phone_number: Optional[str] = None
    expires_at: Optional[str] = None


@dataclass
class CodeResponse:
    """Response vom Code-Polling."""
    status: VerificationStatus
    code: Optional[str] = None
    received_at: Optional[str] = None


class VerificationClientError(Exception):
    """Basis-Exception für VerificationClient."""
    pass


class RateLimitError(VerificationClientError):
    """Rate-Limit erreicht."""
    def __init__(self, retry_after: int):
        self.retry_after = retry_after
        super().__init__(f"Rate-Limit erreicht. Retry nach {retry_after} Sekunden.")


class NoResourceError(VerificationClientError):
    """Keine Ressource (Email/Telefon) verfügbar."""
    pass


class VerificationTimeoutError(VerificationClientError):
    """Timeout beim Warten auf Code."""
    pass


class VerificationClient:
    """
    Client für den Verifikationsserver.

    Verwendung:
        client = VerificationClient(
            server_url="https://verify.example.com",
            api_key="your-api-key"
        )

        # Email-Verifikation
        response = client.request_email("instagram")
        email = response.email_address
        # ... Account registrieren mit email ...
        code = client.poll_for_code(response.request_id, "email", timeout=120)

        # SMS-Verifikation
        response = client.request_sms("instagram")
        phone = response.phone_number
        # ... Account registrieren mit phone ...
        code = client.poll_for_code(response.request_id, "sms", timeout=180)
    """

    DEFAULT_TIMEOUT = 120  # Sekunden
    POLL_INTERVAL = 2  # Sekunden
    MAX_POLL_INTERVAL = 5

    def __init__(self, server_url: str, api_key: str):
        """
        Initialisiert den VerificationClient.

        Args:
            server_url: Basis-URL des Verifikationsservers (ohne /api/v1)
            api_key: API-Key für Authentifizierung
        """
        self.server_url = server_url.rstrip("/")
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "X-API-Key": api_key,
            "Content-Type": "application/json",
            "User-Agent": "AccountForger/1.0"
        })

        logger.info(f"VerificationClient initialisiert für {server_url}")

    def _make_request(
        self,
        method: str,
        endpoint: str,
        json: Dict = None,
        timeout: int = 30
    ) -> Dict[str, Any]:
        """Führt HTTP-Request aus mit Error-Handling."""
        url = f"{self.server_url}/api/v1{endpoint}"

        try:
            response = self.session.request(
                method=method,
                url=url,
                json=json,
                timeout=timeout
            )

            # Error-Handling basierend auf Status-Code
            if response.status_code == 401:
                raise VerificationClientError("Ungültiger API-Key")

            if response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 60))
                raise RateLimitError(retry_after)

            if response.status_code == 503:
                data = response.json()
                raise NoResourceError(data.get("message", "Keine Ressource verfügbar"))

            if response.status_code == 408:
                raise VerificationTimeoutError("Server-seitiger Timeout")

            if response.status_code == 404:
                raise VerificationClientError("Request nicht gefunden")

            response.raise_for_status()
            return response.json()

        except requests.exceptions.Timeout:
            raise VerificationClientError("Verbindungs-Timeout")
        except requests.exceptions.ConnectionError:
            raise VerificationClientError("Verbindung zum Server fehlgeschlagen")

    def request_email(self, platform: str, preferred_domain: str = None) -> VerificationResponse:
        """
        Fordert eine Email-Adresse für Verifikation an.

        Args:
            platform: Zielplattform (instagram, facebook, tiktok, x, etc.)
            preferred_domain: Bevorzugte Domain (optional)

        Returns:
            VerificationResponse mit request_id und email_address
        """
        payload = {"platform": platform}
        if preferred_domain:
            payload["preferred_domain"] = preferred_domain

        data = self._make_request("POST", "/verification/email/request", json=payload)

        logger.info(f"Email angefordert für {platform}: {data.get('email_address')}")

        return VerificationResponse(
            request_id=data["request_id"],
            email_address=data["email_address"],
            expires_at=data.get("expires_at")
        )

    def request_sms(self, platform: str) -> VerificationResponse:
        """
        Fordert eine Telefonnummer für SMS-Verifikation an.

        Args:
            platform: Zielplattform

        Returns:
            VerificationResponse mit request_id und phone_number
        """
        payload = {"platform": platform}

        data = self._make_request("POST", "/verification/sms/request", json=payload)

        logger.info(f"Telefonnummer angefordert für {platform}: {data.get('phone_number')}")

        return VerificationResponse(
            request_id=data["request_id"],
            phone_number=data["phone_number"],
            expires_at=data.get("expires_at")
        )

    def get_code_status(self, request_id: str, verification_type: str) -> CodeResponse:
        """
        Fragt Status eines Verifikationscodes ab (einmalig).

        Args:
            request_id: ID der Verifikationsanfrage
            verification_type: "email" oder "sms"

        Returns:
            CodeResponse mit Status und ggf. Code
        """
        endpoint = f"/verification/{verification_type}/code/{request_id}"
        data = self._make_request("GET", endpoint)

        return CodeResponse(
            status=VerificationStatus(data["status"]),
            code=data.get("code"),
            received_at=data.get("received_at")
        )

    def poll_for_code(
        self,
        request_id: str,
        verification_type: str,
        timeout: int = None,
        callback: callable = None
    ) -> Optional[str]:
        """
        Pollt Server bis Code empfangen wurde.

        Args:
            request_id: ID der Verifikationsanfrage
            verification_type: "email" oder "sms"
            timeout: Maximale Wartezeit in Sekunden
            callback: Optional - wird bei jedem Poll aufgerufen mit (elapsed_seconds, status)

        Returns:
            Verifikationscode oder None bei Timeout
        """
        timeout = timeout or self.DEFAULT_TIMEOUT
        start_time = time.time()
        poll_interval = self.POLL_INTERVAL

        logger.info(f"Starte Polling für {verification_type} Request {request_id}")

        while True:
            elapsed = time.time() - start_time

            if elapsed >= timeout:
                logger.warning(f"Timeout nach {elapsed:.0f}s für Request {request_id}")
                return None

            try:
                response = self.get_code_status(request_id, verification_type)

                if callback:
                    callback(elapsed, response.status)

                if response.status == VerificationStatus.RECEIVED and response.code:
                    logger.info(f"Code empfangen: {response.code} nach {elapsed:.0f}s")
                    return response.code

                if response.status in (VerificationStatus.EXPIRED, VerificationStatus.FAILED):
                    logger.warning(f"Request fehlgeschlagen: {response.status}")
                    return None

            except VerificationClientError as e:
                logger.warning(f"Polling-Fehler: {e}")
                # Weitermachen trotz Fehler

            # Warten vor nächstem Poll (mit Backoff)
            time.sleep(poll_interval)
            poll_interval = min(poll_interval * 1.2, self.MAX_POLL_INTERVAL)

        return None

    def get_available_phones(self) -> Dict[str, Any]:
        """
        Fragt verfügbare Telefonnummern ab.

        Returns:
            dict mit available_count und phones Liste
        """
        return self._make_request("GET", "/phone/available")

    def health_check(self) -> bool:
        """
        Prüft Erreichbarkeit des Servers.

        Returns:
            True wenn Server erreichbar und gesund
        """
        try:
            # Health-Endpoint braucht keinen API-Key
            response = requests.get(
                f"{self.server_url}/api/v1/health",
                timeout=5
            )
            data = response.json()
            return data.get("status") in ("healthy", "degraded")
        except Exception:
            return False


# Singleton-Pattern für globale Instanz
_verification_client: Optional[VerificationClient] = None


def get_verification_client() -> Optional[VerificationClient]:
    """Gibt die globale VerificationClient-Instanz zurück."""
    return _verification_client


def init_verification_client(server_url: str, api_key: str) -> VerificationClient:
    """
    Initialisiert die globale VerificationClient-Instanz.

    Sollte beim App-Start aufgerufen werden.
    """
    global _verification_client
    _verification_client = VerificationClient(server_url, api_key)
    return _verification_client

7.2 Server-Konfigurationsdatei (NEU)

Erstelle config/server_config.json:

{
    "verification_server": {
        "url": "https://verify.example.com",
        "api_key": "",
        "timeout_email": 120,
        "timeout_sms": 180,
        "enabled": true
    }
}

7.3 Zu ändernde Dateien im Client

Datei Änderung
social_networks/base_automation.py verification_client Parameter hinzufügen
social_networks/instagram/instagram_verification.py API-Calls statt direktem EmailHandler
social_networks/facebook/facebook_verification.py API-Calls statt direktem EmailHandler
social_networks/tiktok/tiktok_verification.py SMS via API
controllers/platform_controllers/base_worker_thread.py VerificationClient initialisieren
utils/email_handler.py Deprecation-Warning, später entfernen
config/server_config.json NEU: Server-Konfiguration
utils/verification_client.py NEU: Server-Client

7.4 Beispiel-Integration für Instagram

# social_networks/instagram/instagram_verification.py (GEÄNDERT)

class InstagramVerification:
    def __init__(self, automation):
        self.automation = automation
        # NEU: VerificationClient statt EmailHandler
        self.verification_client = automation.verification_client

    def wait_for_email_code(self, email: str, timeout: int = 120) -> Optional[str]:
        """Wartet auf Email-Verifikationscode via Server."""

        if not self.verification_client:
            # Fallback auf alten EmailHandler (Übergangsphase)
            return self._legacy_wait_for_code(email, timeout)

        try:
            # Request wurde bereits beim Account-Erstellen gemacht
            # Hier nur noch Polling
            request_id = self.automation.current_verification_request_id

            if not request_id:
                logger.error("Keine Verification-Request-ID vorhanden")
                return None

            code = self.verification_client.poll_for_code(
                request_id=request_id,
                verification_type="email",
                timeout=timeout,
                callback=lambda elapsed, status: logger.debug(f"Polling: {elapsed}s, Status: {status}")
            )

            return code

        except Exception as e:
            logger.error(f"Fehler beim Warten auf Email-Code: {e}")
            return None

8. Testbeispiele

8.1 Curl-Befehle

# Health-Check
curl -X GET https://verify.example.com/api/v1/health

# Email anfordern
curl -X POST https://verify.example.com/api/v1/verification/email/request \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"platform": "instagram"}'

# Email-Code abfragen
curl -X GET https://verify.example.com/api/v1/verification/email/code/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: your-api-key"

# SMS anfordern
curl -X POST https://verify.example.com/api/v1/verification/sms/request \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"platform": "instagram"}'

# SMS-Code abfragen
curl -X GET https://verify.example.com/api/v1/verification/sms/code/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: your-api-key"

# Verfügbare Telefonnummern
curl -X GET https://verify.example.com/api/v1/phone/available \
  -H "X-API-Key: your-api-key"

# Webhook simulieren (Router)
curl -X POST https://verify.example.com/api/v1/webhook/rutx11/sms \
  -H "X-Router-Token: router-token-abc" \
  -H "Content-Type: application/json" \
  -d '{
    "sender": "Instagram",
    "message": "123456 ist dein Instagram-Bestätigungscode",
    "timestamp": "2026-01-17T10:30:00Z",
    "sim_slot": 1
  }'

8.2 Python-Integrations-Test

# tests/test_verification_integration.py

import pytest
from utils.verification_client import VerificationClient, RateLimitError

@pytest.fixture
def client():
    return VerificationClient(
        server_url="https://verify.example.com",
        api_key="test-api-key"
    )

def test_health_check(client):
    """Server sollte erreichbar sein."""
    assert client.health_check() is True

def test_email_request(client):
    """Email-Request sollte funktionieren."""
    response = client.request_email("instagram")

    assert response.request_id is not None
    assert response.email_address is not None
    assert "@" in response.email_address

def test_sms_request(client):
    """SMS-Request sollte funktionieren."""
    response = client.request_sms("instagram")

    assert response.request_id is not None
    assert response.phone_number is not None
    assert response.phone_number.startswith("+")

def test_rate_limit(client):
    """Rate-Limit sollte greifen."""
    # Viele Requests in kurzer Zeit
    for _ in range(100):
        try:
            client.request_email("instagram")
        except RateLimitError as e:
            assert e.retry_after > 0
            return

    pytest.fail("Rate-Limit wurde nicht erreicht")

def test_full_email_flow(client, mock_imap_server):
    """Vollständiger Email-Verifikationsflow."""
    # 1. Email anfordern
    response = client.request_email("instagram")

    # 2. Simuliere eingehende Email
    mock_imap_server.send_verification_email(
        to=response.email_address,
        code="123456",
        platform="instagram"
    )

    # 3. Code abrufen
    code = client.poll_for_code(
        request_id=response.request_id,
        verification_type="email",
        timeout=10
    )

    assert code == "123456"

9. Deployment

9.1 Docker Compose

# docker-compose.yml
version: '3.8'

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://verify:${DB_PASSWORD}@db:5432/verification
      - REDIS_URL=redis://redis:6379
      - SECRET_KEY=${SECRET_KEY}
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
      - LOG_LEVEL=INFO
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=verification
      - POSTGRES_USER=verify
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U verify -d verification"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    restart: unless-stopped

  celery-worker:
    build: .
    command: celery -A app.worker worker -l info -Q email_polling
    environment:
      - DATABASE_URL=postgresql://verify:${DB_PASSWORD}@db:5432/verification
      - REDIS_URL=redis://redis:6379
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
    depends_on:
      - db
      - redis
    restart: unless-stopped

  celery-beat:
    build: .
    command: celery -A app.worker beat -l info
    environment:
      - DATABASE_URL=postgresql://verify:${DB_PASSWORD}@db:5432/verification
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    restart: unless-stopped

volumes:
  pgdata:
  redisdata:

9.2 Dockerfile

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# System-Dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Python-Dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# App-Code
COPY app/ ./app/

# Non-root User
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

# Health-Check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/api/v1/health || exit 1

# Start
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

9.3 Umgebungsvariablen (.env.example)

# .env.example

# Datenbank
DB_PASSWORD=your-secure-password-here

# Sicherheit
SECRET_KEY=your-secret-key-for-jwt-etc
ENCRYPTION_KEY=your-32-byte-encryption-key

# Optional: Externe Services
SENTRY_DSN=https://...

# Logging
LOG_LEVEL=INFO

# IMAP (falls zentral konfiguriert)
# IMAP_DEFAULT_SERVER=imap.example.com
# IMAP_DEFAULT_PORT=993

9.4 Sicherheitsanforderungen

Anforderung Implementierung
HTTPS-Only Nginx/Traefik Reverse Proxy mit Let's Encrypt
API-Key-Hashing bcrypt mit Cost Factor 12
IMAP-Passwörter AES-256-GCM verschlüsselt in DB
Rate-Limiting Per-Client basierend auf Tier
IP-Whitelist (Webhooks) Optional: Nur Router-IPs für /webhook/*
Audit-Logging Alle sensitiven Operationen loggen
Request-Expiration Automatische Bereinigung alter Requests

10. Implementierungsreihenfolge

Phase 1: Basis-Setup (MVP)

  1. FastAPI-Projekt aufsetzen
  2. PostgreSQL-Schema deployen
  3. Health-Check Endpoint
  4. Basis-Auth (API-Key-Validierung)

Phase 2: Email-Service

  1. IMAP-Connection-Pool
  2. Email-Polling-Service
  3. Code-Extraktion (Regex)
  4. /verification/email/* Endpoints
  5. Background-Task für Polling (Celery)

Phase 3: SMS-Webhook

  1. /webhook/rutx11/sms Endpoint
  2. Router-Token-Validierung
  3. SMS-Speicherung
  4. Request-Matching-Logik
  5. /verification/sms/* Endpoints

Phase 4: Phone-Rotation

  1. PhoneRotationService
  2. Cooldown-Management
  3. Platform-Aware Auswahl
  4. /phone/available Endpoint

Phase 5: Rate-Limiting & Monitoring

  1. Rate-Limit-Tracking
  2. Tier-basierte Limits
  3. Prometheus-Metriken
  4. Logging-Aggregation
  5. Health-Check erweitern

Appendix A: Projektstruktur (Backend)

accountforger-server/
├── app/
│   ├── __init__.py
│   ├── main.py                 # FastAPI Entry Point
│   ├── config.py               # Settings/Config
│   ├── dependencies.py         # FastAPI Dependencies
│   │
│   ├── api/
│   │   ├── __init__.py
│   │   └── v1/
│   │       ├── __init__.py
│   │       ├── router.py       # API Router
│   │       ├── verification.py # Verifikations-Endpoints
│   │       ├── webhooks.py     # RUTX11 Webhook
│   │       ├── phone.py        # Phone-Availability
│   │       └── health.py       # Health-Check
│   │
│   ├── services/
│   │   ├── __init__.py
│   │   ├── email_service.py    # IMAP-Polling
│   │   ├── sms_service.py      # Webhook-Verarbeitung
│   │   ├── phone_rotation.py   # Nummern-Auswahl
│   │   ├── auth_service.py     # API-Key-Validierung
│   │   └── encryption.py       # AES für IMAP-Passwörter
│   │
│   ├── models/
│   │   ├── __init__.py
│   │   ├── client.py           # Client Model
│   │   ├── router.py           # Router Model
│   │   ├── phone_number.py     # PhoneNumber Model
│   │   ├── verification.py     # VerificationRequest Model
│   │   └── sms_message.py      # SMSMessage Model
│   │
│   ├── schemas/
│   │   ├── __init__.py
│   │   ├── verification.py     # Pydantic Schemas
│   │   └── webhook.py          # Webhook Schemas
│   │
│   ├── db/
│   │   ├── __init__.py
│   │   ├── database.py         # SQLAlchemy Setup
│   │   └── migrations/         # Alembic Migrations
│   │
│   └── worker/
│       ├── __init__.py
│       ├── celery.py           # Celery App
│       └── tasks.py            # Background Tasks
│
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_email_service.py
│   ├── test_sms_service.py
│   └── test_api.py
│
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
├── alembic.ini
├── .env.example
└── README.md

Appendix B: requirements.txt

# Web Framework
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6

# Database
sqlalchemy==2.0.25
asyncpg==0.29.0
alembic==1.13.1

# Redis & Celery
redis==5.0.1
celery==5.3.6

# IMAP
aioimaplib==1.0.1

# Security
bcrypt==4.1.2
cryptography==41.0.7
python-jose[cryptography]==3.3.0

# HTTP Client (für Health-Checks)
httpx==0.26.0

# Validation
pydantic==2.5.3
pydantic-settings==2.1.0
email-validator==2.1.0

# Utilities
python-dateutil==2.8.2

# Testing
pytest==7.4.4
pytest-asyncio==0.23.3

# Monitoring (optional)
prometheus-client==0.19.0
sentry-sdk[fastapi]==1.39.1

Dokument erstellt für Backend-Implementierung. Version 1.0