From 0fc9ea80bf2fca6465580105a9a03c9f9a1d5fd1 Mon Sep 17 00:00:00 2001 From: bunker-admin Date: Fri, 27 Mar 2026 10:06:38 -0600 Subject: [PATCH] Fix cookie Secure flag for HTTP dev, un-track generated nginx confs - Cookie Secure flag now uses req.secure (respects trust proxy + X-Forwarded-Proto) instead of NODE_ENV. Works correctly over both HTTP (local dev) and HTTPS (production tunnel). - SameSite=Strict over HTTPS, SameSite=Lax over HTTP (browsers reject Strict cookies over plain HTTP). - Un-track generated nginx/conf.d/api.conf and services.conf (gitignored, regenerated from templates at startup). - Update CLAUDE.md: ENCRYPTION_KEY now required in all environments. Bunker Admin --- CLAUDE.md | 2 +- api/src/modules/auth/auth.routes.ts | 28 +- .../volunteer-invite.routes.ts | 6 +- nginx/conf.d/api.conf | 43 -- nginx/conf.d/services.conf | 731 ------------------ 5 files changed, 19 insertions(+), 791 deletions(-) delete mode 100644 nginx/conf.d/api.conf delete mode 100644 nginx/conf.d/services.conf diff --git a/CLAUDE.md b/CLAUDE.md index 55e52015..4f5d757d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -619,7 +619,7 @@ Comprehensive security audit completed 2025-02-11, addressing 13 findings. See ` - Redis authentication required - XSS/injection prevention (HTML escaping) - Path traversal protection -- Encryption key for DB secrets (`ENCRYPTION_KEY` required in production) +- Encryption key for DB secrets (`ENCRYPTION_KEY` required in all environments) - Nginx security headers (HSTS, Permissions-Policy, CSP) ### Required Environment Variables diff --git a/api/src/modules/auth/auth.routes.ts b/api/src/modules/auth/auth.routes.ts index 86df403c..93b0ca00 100644 --- a/api/src/modules/auth/auth.routes.ts +++ b/api/src/modules/auth/auth.routes.ts @@ -22,23 +22,25 @@ const router = Router(); const REFRESH_COOKIE_NAME = 'cml_refresh'; const REFRESH_COOKIE_MAX_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days in ms -/** Set the refresh token as an httpOnly cookie */ -function setRefreshCookie(res: Response, token: string) { +/** Set the refresh token as an httpOnly cookie. + * Uses req.secure (respects trust proxy + X-Forwarded-Proto) to determine + * the Secure flag, so it works correctly over both HTTP (dev) and HTTPS (tunnel). */ +function setRefreshCookie(req: Request, res: Response, token: string) { res.cookie(REFRESH_COOKIE_NAME, token, { httpOnly: true, - secure: env.NODE_ENV === 'production', - sameSite: 'strict', + secure: req.secure, + sameSite: req.secure ? 'strict' : 'lax', maxAge: REFRESH_COOKIE_MAX_AGE, path: '/api/auth', }); } /** Clear the refresh token cookie */ -function clearRefreshCookie(res: Response) { +function clearRefreshCookie(req: Request, res: Response) { res.clearCookie(REFRESH_COOKIE_NAME, { httpOnly: true, - secure: env.NODE_ENV === 'production', - sameSite: 'strict', + secure: req.secure, + sameSite: req.secure ? 'strict' : 'lax', path: '/api/auth', }); } @@ -52,7 +54,7 @@ router.post( try { const result = await authService.login(req.body.email, req.body.password); // Set refresh token as httpOnly cookie (not in response body) - setRefreshCookie(res, result.refreshToken); + setRefreshCookie(req, res, result.refreshToken); const { refreshToken: _, ...responseWithoutRefresh } = result; res.json(responseWithoutRefresh); } catch (err) { @@ -71,7 +73,7 @@ router.post( const result = await authService.register(req.body); // Set refresh token as httpOnly cookie if tokens were issued (non-verification path) if ('refreshToken' in result && result.refreshToken) { - setRefreshCookie(res, result.refreshToken); + setRefreshCookie(req, res, result.refreshToken); const { refreshToken: _, ...responseWithoutRefresh } = result; res.status(201).json(responseWithoutRefresh); } else { @@ -278,11 +280,11 @@ router.post( } const result = await authService.refreshTokens(refreshToken); // Set new refresh token as httpOnly cookie - setRefreshCookie(res, result.refreshToken); + setRefreshCookie(req, res, result.refreshToken); const { refreshToken: _, ...responseWithoutRefresh } = result; res.json(responseWithoutRefresh); } catch (err) { - clearRefreshCookie(res); + clearRefreshCookie(req, res); next(err); } } @@ -299,10 +301,10 @@ router.post( if (refreshToken) { await authService.logout(refreshToken); } - clearRefreshCookie(res); + clearRefreshCookie(req, res); res.json({ message: 'Logged out' }); } catch (err) { - clearRefreshCookie(res); + clearRefreshCookie(req, res); next(err); } } diff --git a/api/src/modules/volunteer-invite/volunteer-invite.routes.ts b/api/src/modules/volunteer-invite/volunteer-invite.routes.ts index 972ad81d..5ed70a1a 100644 --- a/api/src/modules/volunteer-invite/volunteer-invite.routes.ts +++ b/api/src/modules/volunteer-invite/volunteer-invite.routes.ts @@ -34,11 +34,11 @@ router.post( async (req: Request, res: Response, next: NextFunction) => { try { const result = await volunteerInviteService.redeemInvite(req.body); - // Set refresh token as httpOnly cookie + // Set refresh token as httpOnly cookie (Secure flag based on actual protocol) res.cookie('cml_refresh', result.tokens.refreshToken, { httpOnly: true, - secure: env.NODE_ENV === 'production', - sameSite: 'strict', + secure: req.secure, + sameSite: req.secure ? 'strict' : 'lax', maxAge: 7 * 24 * 60 * 60 * 1000, path: '/api/auth', }); diff --git a/nginx/conf.d/api.conf b/nginx/conf.d/api.conf deleted file mode 100644 index 1f3399e6..00000000 --- a/nginx/conf.d/api.conf +++ /dev/null @@ -1,43 +0,0 @@ -server { - listen 80; - server_name api.cmlite.org api.betteredmonton.org api.pridecorner.ca; - add_header X-Frame-Options "SAMEORIGIN" always; - - # Media API endpoints (must come BEFORE / for longest prefix match) - location /media/ { - limit_req zone=api_global burst=60 nodelay; - set $upstream_media http://changemaker-media-api:4100/api/; - proxy_pass $upstream_media; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - - # Large upload support - client_max_body_size 10G; - proxy_read_timeout 3600s; - proxy_connect_timeout 75s; - proxy_request_buffering off; - - # WebSocket support - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # Main API (Express) — includes WebSocket upgrade for docs collaboration - location / { - limit_req zone=api_global burst=60 nodelay; - set $upstream_api http://changemaker-v2-api:4000; - proxy_pass $upstream_api; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 300s; - proxy_connect_timeout 75s; - - # WebSocket support (docs collaboration via Hocuspocus) - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} diff --git a/nginx/conf.d/services.conf b/nginx/conf.d/services.conf deleted file mode 100644 index 61429c40..00000000 --- a/nginx/conf.d/services.conf +++ /dev/null @@ -1,731 +0,0 @@ -# Gitea — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name git.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - # Increase max body size for large git pushes (2GB) - client_max_body_size 2048M; - - location / { - set $upstream_gitea http://gitea-changemaker:3000; - proxy_pass $upstream_gitea; - proxy_hide_header X-Frame-Options; - 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; - } -} - -# n8n — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name n8n.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_n8n http://n8n-changemaker:5678; - proxy_pass $upstream_n8n; - proxy_hide_header X-Frame-Options; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -# Grafana — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name grafana.cmlite.org grafana.betteredmonton.org grafana.pridecorner.ca; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always; - - location / { - set $upstream_grafana http://grafana-changemaker:3000; - proxy_pass $upstream_grafana; - proxy_hide_header X-Frame-Options; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -# NocoDB (data browser) — allows iframe embedding from admin -server { - listen 80; - server_name db.cmlite.org db.betteredmonton.org db.pridecorner.ca; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always; - - location / { - set $upstream_nocodb http://changemaker-v2-nocodb:8080; - proxy_pass $upstream_nocodb; - proxy_hide_header X-Frame-Options; - 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; - } -} - -# Listmonk — via auth proxy, allows iframe embedding from admin -server { - listen 80; - server_name listmonk.cmlite.org listmonk.betteredmonton.org listmonk.pridecorner.ca; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org app.pridecorner.ca" always; - - location / { - set $upstream_listmonk http://changemaker-v2-api:9002; - proxy_pass $upstream_listmonk; - 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; - } -} - -# MkDocs — allows iframe embedding from admin -server { - listen 80; - server_name docs.cmlite.org docs.betteredmonton.org docs.pridecorner.ca; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always; - - location / { - set $upstream_mkdocs http://mkdocs-changemaker:8000; - proxy_pass $upstream_mkdocs; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -# Code Server — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name code.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_code http://code-server-changemaker:8443; - proxy_pass $upstream_code; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -# MailHog (email testing) — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name mail.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_mailhog http://mailhog-changemaker:8025; - proxy_pass $upstream_mailhog; - proxy_hide_header X-Frame-Options; - 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; - # WebSocket support for MailHog live updates - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -# Mini QR — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name qr.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_miniqr http://mini-qr:8080; - proxy_pass $upstream_miniqr; - proxy_hide_header X-Frame-Options; - 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; - } -} - -# Excalidraw — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name draw.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_excalidraw http://excalidraw-changemaker:80; - proxy_pass $upstream_excalidraw; - proxy_hide_header X-Frame-Options; - 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; - - # WebSocket support for collaboration - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - } -} - -# Vaultwarden (password manager) — allows iframe embedding from admin -server { - listen 80; - server_name vault.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_vaultwarden http://vaultwarden-changemaker:80; - proxy_pass $upstream_vaultwarden; - proxy_hide_header X-Frame-Options; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - } -} - -# Rocket.Chat (team chat) — allows iframe embedding from admin -server { - listen 80; - server_name chat.cmlite.org chat.betteredmonton.org chat.pridecorner.ca; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always; - - location / { - set $upstream_rocketchat http://rocketchat-changemaker:3000; - proxy_pass $upstream_rocketchat; - proxy_hide_header X-Frame-Options; - 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; - # WebSocket support (critical for RC real-time messaging) - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - client_max_body_size 100m; - } -} - -# Gancio (event management) — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name events.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org" always; - - location / { - set $upstream_gancio http://gancio-changemaker:13120; - proxy_pass $upstream_gancio; - proxy_hide_header X-Frame-Options; - 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; - } -} - -# Jitsi Meet (video conferencing) — allows iframe embedding from admin (app.cmlite.org) -server { - listen 80; - server_name meet.cmlite.org meet.betteredmonton.org meet.pridecorner.ca; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always; - - location / { - set $upstream_jitsi http://jitsi-web-changemaker:80; - proxy_pass $upstream_jitsi; - proxy_hide_header X-Frame-Options; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - } -} - -# --- Embed proxy ports (for iframe embedding without DNS/subdomain) --- -# These listen on dedicated ports so the admin GUI can iframe services via -# localhost:PORT, bypassing X-Frame-Options without needing *.localhost DNS. -# NOTE: In Docker deployments, these ports come from env vars via the .template file. -# These hardcoded values are defaults for reference only. - -server { - listen 8881; - location / { - set $upstream_nocodb http://changemaker-v2-nocodb:8080; - proxy_pass $upstream_nocodb; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - } -} - -server { - listen 8882; - location / { - set $upstream_n8n http://n8n-changemaker:5678; - proxy_pass $upstream_n8n; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -server { - listen 8883; - # Increase max body size for large git pushes (2GB) - client_max_body_size 2048M; - location / { - set $upstream_gitea http://gitea-changemaker:3000; - proxy_pass $upstream_gitea; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - } -} - -server { - listen 8884; - location / { - set $upstream_mailhog http://mailhog-changemaker:8025; - proxy_pass $upstream_mailhog; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -server { - listen 8885; - location / { - set $upstream_miniqr http://mini-qr:8080; - proxy_pass $upstream_miniqr; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - } -} - -# Excalidraw embed proxy (port 8886) -server { - listen 8886; - location / { - set $upstream_excalidraw http://excalidraw-changemaker:80; - proxy_pass $upstream_excalidraw; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - - # WebSocket support for collaboration - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - } -} - -# Admin GUI — app subdomain -server { - listen 80; - server_name app.cmlite.org app.betteredmonton.org app.pridecorner.ca; - add_header X-Frame-Options "SAMEORIGIN" always; - - # Social media bot detection for OG meta tags - set $is_bot 0; - if ($http_user_agent ~* "(Twitterbot|facebookexternalhit|LinkedInBot|Slackbot|TelegramBot|WhatsApp|Discordbot|Googlebot|bingbot|Pinterest|Embedly|Quora Link Preview|Showyoubot|outbrain|vkShare|W3C_Validator)") { - set $is_bot 1; - } - - # Bot-specific rewrites — serve OG meta from API for rich social previews - location ~ ^/campaign/([^/]+)$ { - if ($is_bot) { - rewrite ^/campaign/(.+)$ /api/og/campaign/$1 last; - } - set $upstream_admin http://changemaker-v2-admin:3000; - proxy_pass $upstream_admin; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - location ~ ^/p/([^/]+)$ { - if ($is_bot) { - rewrite ^/p/(.+)$ /api/og/page/$1 last; - } - set $upstream_admin http://changemaker-v2-admin:3000; - proxy_pass $upstream_admin; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - location ~ ^/gallery/watch/([^/]+)$ { - if ($is_bot) { - rewrite ^/gallery/watch/(.+)$ /api/og/gallery/$1 last; - } - set $upstream_admin http://changemaker-v2-admin:3000; - proxy_pass $upstream_admin; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - location / { - set $upstream_admin http://changemaker-v2-admin:3000; - proxy_pass $upstream_admin; - 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; - # WebSocket support for Vite HMR - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # Media API (direct path - used by admin GUI media-api.ts client) - # Rewrites /media/* to /api/* (matches Vite dev proxy behavior) - # Uses variable proxy_pass for runtime DNS resolution after container restarts - location /media/ { - rewrite ^/media/(.*) /api/$1 break; - set $upstream_media_app http://changemaker-media-api:4100; - proxy_pass $upstream_media_app; - 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; - - # Large file upload support - client_max_body_size 10G; - proxy_read_timeout 3600s; - proxy_connect_timeout 75s; - proxy_request_buffering off; - - # WebSocket support - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # Media API endpoints (must come BEFORE /api/ for longest prefix match) - location /api/media/ { - set $upstream_media http://changemaker-media-api:4100; - proxy_pass $upstream_media; - 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; - - # Large upload support - client_max_body_size 10G; - proxy_read_timeout 3600s; - proxy_connect_timeout 75s; - proxy_request_buffering off; - - # WebSocket support - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # MkDocs proxy (docs search index for volunteer map) - location /mkdocs-proxy/ { - set $upstream_mkdocs http://mkdocs-changemaker:8000; - rewrite ^/mkdocs-proxy/(.*) /$1 break; - proxy_pass $upstream_mkdocs; - 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; - } - - # API (Express) - location /api/ { - set $upstream_api http://changemaker-v2-api:4000; - proxy_pass $upstream_api; - 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; - } -} - -# MkDocs built static site — root domain -server { - listen 80; - server_name cmlite.org; - - location / { - set $upstream_site http://mkdocs-site-server-changemaker:80; - proxy_pass $upstream_site; - 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; - } -} - -# Root domain — routes to admin GUI (supports custom DOMAIN env var) -server { - listen 80; - server_name betteredmonton.org pridecorner.ca; - add_header X-Frame-Options "SAMEORIGIN" always; - - location / { - set $upstream_admin http://changemaker-v2-admin:3000; - proxy_pass $upstream_admin; - 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; - # WebSocket support for Vite HMR - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # Media API (direct path - used by admin GUI media-api.ts client) - # Uses variable proxy_pass for runtime DNS resolution after container restarts - location /media/ { - rewrite ^/media/(.*) /api/$1 break; - set $upstream_media_root http://changemaker-media-api:4100; - proxy_pass $upstream_media_root; - 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; - - # Large file upload support - client_max_body_size 10G; - proxy_read_timeout 3600s; - proxy_connect_timeout 75s; - proxy_request_buffering off; - - # WebSocket support - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # Media API endpoints (must come BEFORE /api/ for longest prefix match) - location /api/media/ { - set $upstream_media http://changemaker-media-api:4100; - proxy_pass $upstream_media; - 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; - - # Large upload support - client_max_body_size 10G; - proxy_read_timeout 3600s; - proxy_connect_timeout 75s; - proxy_request_buffering off; - - # WebSocket support - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # API (Express) - location /api/ { - set $upstream_api http://changemaker-v2-api:4000; - proxy_pass $upstream_api; - 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; - } -} - -# Homepage dashboard — allows iframe embedding from admin -server { - listen 80; - server_name home.cmlite.org; - add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always; - - location / { - set $upstream_homepage http://homepage-changemaker:3000; - proxy_pass $upstream_homepage; - proxy_hide_header X-Frame-Options; - 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; - } -} - -# Homepage embed proxy (port 8887) -server { - listen 8887; - location / { - set $upstream_homepage http://homepage-changemaker:3000; - proxy_pass $upstream_homepage; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - } -} - -# Vaultwarden embed proxy (port 8890) -server { - listen 8890; - location / { - set $upstream_vaultwarden http://vaultwarden-changemaker:80; - proxy_pass $upstream_vaultwarden; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - } -} - -# Rocket.Chat embed proxy (port 8891) -server { - listen 8891; - location / { - set $upstream_rocketchat http://rocketchat-changemaker:3000; - proxy_pass $upstream_rocketchat; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - client_max_body_size 100m; - } -} - -# Gancio embed proxy (port 8892) -server { - listen 8892; - location / { - set $upstream_gancio http://gancio-changemaker:13120; - proxy_pass $upstream_gancio; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - } -} - -# Jitsi Meet embed proxy (port 8893) -server { - listen 8893; - location / { - set $upstream_jitsi http://jitsi-web-changemaker:80; - proxy_pass $upstream_jitsi; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - } -} - -# Grafana embed proxy (port 8894) -server { - listen 8894; - location / { - set $upstream_grafana http://grafana-changemaker:3000; - proxy_pass $upstream_grafana; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} - -# Alertmanager embed proxy (port 8895) -server { - listen 8895; - location / { - set $upstream_alertmanager http://alertmanager-changemaker:9093; - proxy_pass $upstream_alertmanager; - proxy_hide_header X-Frame-Options; - proxy_hide_header Content-Security-Policy; - add_header Content-Security-Policy "frame-ancestors 'self' localhost 127.0.0.1" always; - 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; - } -}