From ad7ed735d7092d267a2f0646a13bd07e37ec39d6 Mon Sep 17 00:00:00 2001 From: Zane Wolfgang Pickett Date: Sun, 9 Nov 2025 12:54:19 -0800 Subject: [PATCH] Fix Reverse Proxy Support --- .github/copilot-instructions.md | 2 ++ README.md | 30 ++++++++++++++++++++---------- cps/__init__.py | 16 ++++++++++------ docker-compose.yml | 5 ++++- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 73c46dc..ae04035 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -186,6 +186,7 @@ Pluggable providers in `cps/metadata_provider/`: - `CWA_WATCH_MODE`: Force polling watcher (`poll`) or inotify (default) - `HARDCOVER_TOKEN`: API key for Hardcover metadata provider - `COOKIE_PREFIX`: Custom prefix for session cookies +- `TRUSTED_PROXY_COUNT`: Number of proxies to trust for X-Forwarded-* headers (default: 1, use 2+ for CF Tunnel + reverse proxy) ## Common Pitfalls 1. **Don't import SQLite on main thread**: Always use `init_db_thread()` in background tasks @@ -195,6 +196,7 @@ Pluggable providers in `cps/metadata_provider/`: 5. **WAL mode errors**: Usually means network share deployment without `NETWORK_SHARE_MODE=true` 6. **Port binding**: Ports below 1024 need `cap_add: [NET_BIND_SERVICE]` in docker-compose 7. **Calibre plugins**: Requires `customize.py.json` in `/config/.config/calibre/` to register +8. **Session protection errors**: Behind multiple proxies? Set `TRUSTED_PROXY_COUNT` to match your proxy chain depth ## Version Management - **Installed version**: `/app/CWA_RELEASE` (baked at build time) diff --git a/README.md b/README.md index f3ed259..48f15ed 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,16 @@ This tells CWA to avoid enabling WAL on the Calibre `metadata.db` and the `app.d - On Docker Desktop (Windows/macOS), the container runs on a LinuxKit/WSL2 VM and host-mounted paths may not propagate `inotify` events reliably. CWA auto-detects Docker Desktop at startup and prefers the same polling watcher for reliability. - Advanced: You can also force polling regardless of share mode by setting `CWA_WATCH_MODE=poll`. +### Running behind multiple proxies (Cloudflare Tunnel, reverse proxy) + +- CWA uses Werkzeug's ProxyFix middleware to properly handle `X-Forwarded-For`, `X-Forwarded-Proto`, and other proxy headers. +- By default, it trusts **1 proxy** in the chain. If you have multiple proxies (e.g., Cloudflare Tunnel → nginx → CWA), set: + + - `TRUSTED_PROXY_COUNT=2` (or the total number of proxies in your chain) + +- **Why this matters**: Session protection validates requests based on the client's IP address. If ProxyFix doesn't trust enough proxies, it may see different IPs between requests, causing "Session protection triggered" warnings and forcing re-login. +- **Troubleshooting**: If you see frequent session protection warnings in logs, check your proxy chain depth and adjust this variable accordingly. + ## **_Features:_** ### CWA supports all Stock CW Features: @@ -304,7 +314,7 @@ services: - CWA_PORT_OVERRIDE=8083 volumes: # CW users migrating should stop their existing CW instance, make a copy of the config folder, and bind that here to carry over all of their user settings ect. - - /path/to/config/folder:/config + - /path/to/config/folder:/config # This is an ingest dir, NOT a library one. Anything added here will be automatically added to your library according to the settings you have configured in CWA Settings page. All files placed here are REMOVED AFTER PROCESSING - /path/to/the/folder/you/want/to/use/for/book/ingest:/cwa-book-ingest # If you don't have an existing library, CWA will automatically create one at the bind provided here @@ -395,23 +405,23 @@ CWA now includes built-in KOReader syncing functionality, allowing you to sync y ## Local Development Setup -1. **Build the image** +1. **Build the image** Edit and run [`build.sh`](https://github.com/crocodilestick/Calibre-Web-Automated/blob/main/build.sh) to build a local Docker image of Calibre-Web-Automated. See the script itself for usage details. -2. **Edit [`docker-compose.yml.dev`](https://github.com/crocodilestick/Calibre-Web-Automated/blob/main/docker-compose.yml.dev)** - Update at minimum: - - `image:` → your image tag from step 1 - - `volumes mounts` → paths for config, ingest, library, plugins - +2. **Edit [`docker-compose.yml.dev`](https://github.com/crocodilestick/Calibre-Web-Automated/blob/main/docker-compose.yml.dev)** + Update at minimum: + - `image:` → your image tag from step 1 + - `volumes mounts` → paths for config, ingest, library, plugins + To have the app refresh dynamically in response to code changes, see comments in the [`docker-compose.yml.dev`](https://github.com/crocodilestick/Calibre-Web-Automated/blob/main/docker-compose.yml.dev)** for details and examples on "live-edit" mounts. -3. **Start the service** +3. **Start the service** ``` $ docker compose -f docker-compose.yml.dev up -d ``` -4. **Log in & configure** - - Sign in with the [default admin login](https://github.com/crocodilestick/Calibre-Web-Automated?tab=readme-ov-file#default-admin-login) +4. **Log in & configure** + - Sign in with the [default admin login](https://github.com/crocodilestick/Calibre-Web-Automated?tab=readme-ov-file#default-admin-login) - Optionally follow [Post-Install Tasks](https://github.com/crocodilestick/Calibre-Web-Automated?tab=readme-ov-file#post-install-tasks)- --- diff --git a/cps/__init__.py b/cps/__init__.py index 4078955..132c226 100755 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -83,7 +83,11 @@ app.config.update( # Fix for running behind reverse proxy (e.g. nginx, apache, caddy, ...) # Without it, url_for will generate http:// urls even if https:// is used -app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1) +# Set TRUSTED_PROXY_COUNT to the number of proxies in your chain (default: 1) +# For CF Tunnel + reverse proxy, use TRUSTED_PROXY_COUNT=2 +num_proxies = int(os.environ.get('TRUSTED_PROXY_COUNT', '1')) +app.wsgi_app = ProxyFix(app.wsgi_app, x_for=num_proxies, x_proto=num_proxies, x_host=num_proxies, x_prefix=num_proxies) +log.info(f'ProxyFix configured to trust {num_proxies} proxy(ies) for X-Forwarded-* headers') lm = MyLoginManager() @@ -120,7 +124,7 @@ def create_app(): config_sql.load_configuration(ub.session, encrypt_key) config.init_config(ub.session, encrypt_key, cli_param) - + # Set OAuth redirect host consistency if hasattr(config, 'config_oauth_redirect_host') and config.config_oauth_redirect_host: from urllib.parse import urlparse @@ -220,7 +224,7 @@ def create_app(): except Exception: # Failsafe: let route-level code handle specific DB errors pass - + # Load user from reverse proxy header early in request lifecycle # This ensures current_user resolves correctly before any code accesses user settings @app.before_request @@ -229,11 +233,11 @@ def create_app(): Load user from reverse proxy authentication header if configured. Sets g.flask_httpauth_user early so that current_user proxy resolves correctly for user-specific settings like theme preferences. - + This must run before any blueprint before_request handlers that access current_user. """ from flask import g, request - + if config.config_allow_reverse_proxy_header_login: from . import usermanagement user = usermanagement.load_user_from_reverse_proxy_header(request) @@ -242,7 +246,7 @@ def create_app(): else: # Explicitly set to None to indicate we checked but found nothing g.flask_httpauth_user = None - + from .schedule import register_scheduled_tasks, register_startup_tasks register_scheduled_tasks(config.schedule_reconnect) register_startup_tasks() diff --git a/docker-compose.yml b/docker-compose.yml index b104d01..06bcdf8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,12 +19,15 @@ services: - NETWORK_SHARE_MODE=false # If you want to force polling mode regardless of share type, set CWA_WATCH_MODE=poll # - CWA_WATCH_MODE=poll + # If running behind multiple proxies (e.g., Cloudflare Tunnel + reverse proxy), set the total number of proxies + # This ensures proper IP detection for session protection and rate limiting (default: 1) + # - TRUSTED_PROXY_COUNT=2 # Skip the automatic library detection/mount at startup. When enabled, the auto-library service will not run. # Accepts: true/yes/1 to disable auto-mount (default: false) # - DISABLE_LIBRARY_AUTOMOUNT=false volumes: # CW users migrating should stop their existing CW instance, make a copy of the config folder, and bind that here to carry over all of their user settings etc. - - /path/to/config/folder:/config + - /path/to/config/folder:/config # This is an ingest dir, NOT a library one. Anything added here will be automatically added to your library according to the settings you have configured in CWA Settings page. All files placed here are REMOVED AFTER PROCESSING - /path/to/the/folder/you/want/to/use/for/book/ingest:/cwa-book-ingest # If you don't have an existing library, CWA will automatically create one at the bind provided here