# ============================================================================
# nginx.conf.example — Production reverse proxy for the receipt-scanner app
# (Next.js 15 App Router, standalone build: `node server.js` on :3000)
#
# English / Deutsch: comments alternate between English and German so both the
# team and German-speaking operators can follow the reasoning. Replace the
# placeholders (<example.com>, ...) and drop this file into
# /etc/nginx/conf.d/ as a real `server {}` block.
#
# SECURITY PRINCIPLE (Sicherheitsprinzip):
#   * `autoindex off;` — Verzeichnislisting ist explizit deaktiviert. Directory
#     listing is explicitly disabled: nginx will never render an index of a
#     directory, it always answers 403/404 for directories without an index
#     file. This is defense in depth — the Next standalone server below already
#     never lists directories and only serves `public/`.
#   * All sensitive requests are rejected AT THE PROXY (before they ever reach
#     the app): dotfiles, source/build artifacts, markdown, keys, logs, env
#     files. The app's own middleware (src/lib/http/sensitivePaths.ts) applies
#     the same policy again inside the container.
#   * Sensitive paths get `deny all` (403), NOT a redirect — a redirect would
#     confirm the resource exists (information leak).
# ============================================================================

# ----------------------------------------------------------------------------
# HTTP → HTTPS redirect (only serves the HSTS upgrade, no app traffic)
# HTTP-Datenverkehr wird ausschließlich auf HTTPS umgeleitet.
# ----------------------------------------------------------------------------
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt / certbot webroot — kein App-Traffic hier.
    # `^~` ist wichtig: damit hat dieses Präfix Vorrang vor der Regex-Location
    # `~ /\.` weiter unten, die dotfiles sperrt — sonst würde die ACME-Challenge
    # unter /.well-known/ fälschlich mit 403 beantwortet. `^~` matters: without
    # it the dotfile-deny regex below would shadow the ACME challenge.
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# ----------------------------------------------------------------------------
# HTTPS server — the actual reverse proxy
# ----------------------------------------------------------------------------
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # TLS-Zertifikate (Let's Encrypt empfohlen). TLS certificates — adjust paths.
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;

    # --- GLOBAL HARDENING ---------------------------------------------------
    # Verzeichnislisting ist explizit deaktiviert (Directory listing off):
    autoindex off;

    # Zweite Verteidigungsschicht gegen übergroße Uploads (Second layer of
    # defense against oversized uploads): the app already caps uploads at
    # MAX_UPLOAD_BYTES = 10 MB (src/lib/limits.ts) and, since the 2026-08-17
    # hardening pass, streams+aborts multipart bodies that lack a trustworthy
    # Content-Length (readFormDataSized in src/lib/http/requestSize.ts).
    # This directive rejects oversized bodies at the proxy — before nginx even
    # finishes buffering them into the upstream connection — with 413. Set a
    # little above the app's 10 MB cap to leave room for multipart
    # boundary/header overhead on a legitimate max-size upload.
    client_max_body_size 11m;

    # Blockiert alle dotfiles/dot-Verzeichnisse (/.env, /.git/, /.next/,
    # /.next-corrupt-*/...) — bereits an der Proxy-Ebene, bevor der Request die
    # App erreicht. Blocks any URI containing a "/." segment (dotfiles etc.).
    # Deny (403), nie ein Redirect — kein Information Leak.
    # Einzige Ausnahme: /.well-known/ (ACME) ist oben per `^~` ausgenommen.
    location ~ /\. {
        deny all;
    }

    # Sensible Datei-Endungen (sensitive file extensions): Markdown (Doku),
    # private keys/Zertifikate, Logs und .env-Dateien — egal auf welcher Tiefe.
    location ~* \.(md|pem|key|crt|log|env.*)$ {
        deny all;
    }

    # Explizite Sperre für Projekt-/Build-Dateien im Repo-Root (explicit deny
    # für bekannte sensitive Namen — wirft 403 statt den Request weiterzuleiten).
    location = /docker-compose.yml              { deny all; }
    location = /docker-compose.yaml             { deny all; }
    location = /docker-compose.override.yml     { deny all; }
    location = /docker-compose.override.yaml    { deny all; }
    location = /Dockerfile                      { deny all; }
    location = /build_err.txt                   { deny all; }
    location = /package.json                    { deny all; }
    location = /package-lock.json               { deny all; }
    location = /tsconfig.json                   { deny all; }
    location = /tsconfig.tsbuildinfo            { deny all; }
    location = /next.config.ts                  { deny all; }
    location = /next.config.mjs                 { deny all; }
    location = /drizzle.config.ts               { deny all; }

    # --- Security headers (Sicherheits-Header) ------------------------------
    # HSTS: nur über HTTPS gesendet (this block is HTTPS-only, so unconditional
    # `always` is correct here). 2 Jahre, alle Subdomains, Preload.
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    # Frame-Einbettung verbieten (Clickjacking-Schutz):
    add_header X-Frame-Options "DENY" always;
    # MIME-Sniffing deaktivieren (nosniff):
    add_header X-Content-Type-Options "nosniff" always;
    # Referrer-Politik: keine sensiblen Daten im Referrer nach außen.
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # --- gzip (Komprimierung) ----------------------------------------------
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        image/svg+xml
        font/woff2;

    # --- Proxy to the Next.js standalone server (node server.js) ------------
    # Alles andere wird an den App-Container auf Port 3000 durchgereicht.
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Next.js braucht die Original-Host-Header und WebSocket-Support (dev/WS).
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        # Wichtig: X-Forwarded-Proto wird gesetzt, damit die App weiß, dass der
        # Client über HTTPS kommt (die App emittiert HSTS dann korrekt).
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_buffering off;
        proxy_read_timeout 60s;
        proxy_connect_timeout 5s;
    }
}
