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:
154
nginx.conf.example
Normal file
154
nginx.conf.example
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user