Add full application: receipt scanning, auth, billing, and account deletion

Brings the working codebase (Next.js app, auth system, Stripe billing,
Docker/deploy config, tests, docs) into version control on top of the
placeholder initial commit, and adds account self-deletion (Danger Zone
in Settings, password + typed-email confirmation, cascading DB cleanup,
Stripe cancellation) per GDPR right-to-erasure.

Excludes local build caches, node_modules, and internal agent scratch
files; .gitignore hardened to keep those out going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Timo
2026-08-19 20:59:04 +02:00
parent 650a74da97
commit 84b9987c49
415 changed files with 96619 additions and 0 deletions

154
nginx.conf.example Normal file
View File

@@ -0,0 +1,154 @@
# ============================================================================
# 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;
}
}