Files
MindSift/Doku/reverse-proxy.md
T
sandrodachandClaude Opus 5.5 f57a9884d1
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m32s
fix(security): stream tickets instead of session tokens in URLs (FLAW-22)
WebSocket and EventSource put the 30-day session token into the URL,
where proxies log it. The app now gets a one-time ticket right before
connecting (POST /auth/stream-ticket, 60 s, bound to the token version);
?token= is no longer accepted for streams, non-browser clients may use
the Authorization header.

With BASE_URL set, CORS allows only that origin and WebSocket upgrades
only it or the requested host; credentials are off (no cookies exist).
Logout, ending all sessions, a new password and deactivation close all
live streams of the account; clients with a valid session reconnect
with a fresh ticket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:46:58 +01:00

9.7 KiB
Raw Blame History

🛡️ Reverse-Proxy & WebServer-Konfigurationshandbuch

Dieses Dokument beschreibt die empfohlene Konfiguration von Reverse-Proxies und Ingress-Controllern für MindSift. MindSift nutzt eine hybride Realtime-Architektur bestehend aus bidirektionalen WebSockets und einem automatischen Server-Sent Events (SSE) Fallback, um selbst in restriktivsten Firmennetzwerken, hinter strikten Firewalls oder bei Deep-Packet-Inspection unterbrechungsfreie Live-Synchronisation zu gewährleisten.


⚡ Wichtige Anforderungen auf einen Blick

Protokoll Standard-Port Benötigte Header & Einstellungen Zweck
WebSockets 443 (WSS) / 80 (WS) Upgrade $http_upgrade
Connection "Upgrade"
proxy_read_timeout 3600s
Bidirektionale Live-Updates (Aufgaben, Listen, Notizen)
Server-Sent Events (SSE) 443 (HTTPS) / 80 (HTTP) X-Accel-Buffering: no
proxy_buffering off
proxy_cache off
Fallback bei blockiertem WebSocket-Upgrade
REST API & Uploads 443 (HTTPS) / 80 (HTTP) client_max_body_size 64M Notizen, Import/Export (JSON)
Client-IP – X-Real-IP / X-Forwarded-For Login-Sperre nach Fehlversuchen trifft den richtigen Absender

Client-IP und TRUSTED_PROXIES

MindSift sperrt Anmeldungen nach Fehlversuchen auch pro IP-Adresse. Hinter einem Proxy kommt jede Verbindung vom Proxy; die echte Adresse steht in X-Forwarded-For bzw. X-Real-IP. MindSift glaubt diesen Kopfzeilen nur, wenn die Verbindung von einem vertrauenswürdigen Proxy kommt, sonst könnte jeder, der den Port direkt erreicht, eine beliebige Adresse vortäuschen.

TRUSTED_PROXIES Wirkung
private (Standard) Proxys aus localhost, Docker- und Heimnetzen (10.x, 172.16–31.x, 192.168.x, fd…, 100.64.x). Passt für Proxy im selben Docker-Netz oder auf demselben Host – nichts einzustellen.
private,198.51.100.0/24 zusätzlich eigene Bereiche, z. B. ein externer Proxy-Server oder die IP-Bereiche von Cloudflare
all Kopfzeilen immer glauben (früheres Verhalten) – Notausgang, wenn die Erkennung Probleme macht
none Kopfzeilen nie glauben

Schickt ein vertrauenswürdiger Proxy gar keine Client-IP mit, steht einmal eine Warnung im Log, und für diese Anfragen gilt nur noch die Sperre pro Konto. Die ganze Login-Sperre lässt sich mit LOGIN_PROTECTION=false abschalten.


1. Nginx

Eine typische nginx.conf bzw. Site-Konfiguration unter /etc/nginx/sites-available/mindsift:

# Map für sauberes Connection-Handling bei WebSocket-Upgrades
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name mindsift.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name mindsift.example.com;

    ssl_certificate /etc/letsencrypt/live/mindsift.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mindsift.example.com/privkey.pem;

    # Maximale Payload-Größe für große Notizen und Exporte
    client_max_body_size 64M;

    # MindSift Frontend & REST API
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 1. WebSocket Endpunkte (/api/v1/ws)
    location /api/v1/ws {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        # Upgrade-Header zwingend erforderlich
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Verhindert vorzeitigen Verbindungsabbruch durch Proxy-Inaktivitäts-Timeout
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # 2. Server-Sent Events Fallback (/api/v1/events & /api/v1/sync/)
    location ~ ^/api/v1/(events|sync/) {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        # Pufferung zwingend deaktivieren für sofortiges Event-Streaming
        proxy_buffering off;
        proxy_cache off;
        proxy_set_header X-Accel-Buffering no;

        proxy_set_header Connection '';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 86400s;
    }
}

2. Traefik (Docker Compose / Ingress)

Bei Verwendung von Traefik Version 2/3 müssen keine expliziten WebSocket-Upgrades deklariert werden, da Traefik diese transparent durchleitet. Für Server-Sent Events und Timeouts empfehlen sich Middleware-Definitionen:

version: '3.8'

services:
  mindsift:
    image: ghcr.io/sandrodach/mindsift:latest
    restart: unless-stopped
    environment:
      - PORT=8080
      - DB_PATH=/app/data/mindsift.db
    volumes:
      - ./data:/app/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mindsift.rule=Host(`mindsift.example.com`)"
      - "traefik.http.routers.mindsift.entrypoints=websecure"
      - "traefik.http.routers.mindsift.tls.certresolver=letsencrypt"
      - "traefik.http.services.mindsift.loadbalancer.server.port=8080"

      # Verhindert Pufferung und hält persistente SSE-Streams am Leben
      - "traefik.http.middlewares.mindsift-buffering.buffering.maxRequestBodyBytes=67108864"
      - "traefik.http.routers.mindsift.middlewares=mindsift-buffering"

3. Caddy Server (Caddyfile)

Caddy unterstützt WebSockets und HTTP/2 Server-Sent Events von Haus aus vollständig ("out-of-the-box"):

mindsift.example.com {
    encode gzip zstd

    # Event-Stream (SSE) ohne Pufferung weiterleiten
    reverse_proxy 127.0.0.1:8080 {
        # Streaming für SSE optimieren
        flush_interval -1
    }
}

4. Cloudflare Proxy (Orange Cloud)

Wird MindSift hinter dem Cloudflare CDN betrieben:

  1. WebSockets aktivieren:
    • Navigiere in das Cloudflare Dashboard: Network > WebSockets > Schalter auf ON stellen.
  2. Cloudflare Rocket Loader / Auto Minify:
    • Deaktiviere "Rocket Loader" für MindSift, da asynchrone Skripteingriffe den Start der Single-Page-App stören können.
  3. Cloudflare WAF / Bot Fight Mode:
    • Stelle sicher, dass GET /api/v1/events und GET /api/v1/ws* nicht von "Challenge"-Seiten blockiert werden.
  4. Client-IP: Cloudflare liefert die Besucheradresse in CF-Connecting-IP/X-Forwarded-For. Spricht Cloudflare MindSift direkt an (ohne eigenen Proxy dazwischen), die Cloudflare-Bereiche in TRUSTED_PROXIES ergänzen.
  5. SSE-Unterstützung:
    • Cloudflare unterstützt Server-Sent Events nativ über HTTP/2 und HTTP/3. Dank der vom MindSift-Backend gesetzten Header X-Accel-Buffering: no und Cache-Control: no-cache streamt Cloudflare Echtzeit-Events verzögerungsfrei an die Clients.

5. Apache 2.4 (mod_proxy_wstunnel)

Unter Debian/Ubuntu:

sudo a2enmod proxy proxy_http proxy_wstunnel headers
sudo systemctl restart apache2

VirtualHost-Konfiguration (/etc/apache2/sites-available/mindsift.conf):

<VirtualHost *:443>
    ServerName mindsift.example.com
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/mindsift.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/mindsift.example.com/privkey.pem

    # 1. WebSocket Tunneling
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /(.*)           ws://127.0.0.1:8080/$1 [P,L]
    RewriteCond %{HTTP:Upgrade} !=websocket [NC]
    RewriteRule /(.*)           http://127.0.0.1:8080/$1 [P,L]

    # 2. SSE Response Flushing
    <Location "/api/v1/events">
        ProxyPass "http://127.0.0.1:8080/api/v1/events" flushpackets=on
        ProxyPassReverse "http://127.0.0.1:8080/api/v1/events"
    </Location>

    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

Wenn der Proxy nicht angepasst werden kann

MindSift funktioniert auch hinter Proxys, die WebSocket blocken, Streams puffern oder nur kurze Anfragen erlauben. Die Notiz-Synchronisation wählt automatisch den besten verfügbaren Weg (WebSocket → SSE → Long-Polling → Abfrage alle 3 s); der Editor zeigt ihn an. Long-Polling-Anfragen dauern höchstens 25 s und bleiben damit unter üblichen Proxy-Grenzen. Details: api/websocket.md.

🔍 Diagnose & Verifikation

Mit den folgenden Befehlen lässt sich die Erreichbarkeit und Streaming-Funktion prüfen:

1. Server-Sent Events Test (Standard-HTTPS)

curl -N -H "Accept: text/event-stream" -H "Authorization: Bearer <DEIN_TOKEN>" https://mindsift.example.com/api/v1/events

Erwartete Ausgabe:

: connected (transport: sse, id: sse_...)
data: {"event":"SSE_CONNECTED","payload":{"sseId":"sse_...","transport":"sse"},"timestamp":"..."}
: ping

2. WebSocket Handshake Test

curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Host: mindsift.example.com" \
  -H "Origin: https://mindsift.example.com" \
  -H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Authorization: Bearer <DEIN_TOKEN>" \
  https://mindsift.example.com/api/v1/ws

Erwartete Antwort: 101 Switching Protocols


🧭 Verwandte Dokumente