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
This commit is contained in:
parent
776aa6fbac
commit
0fc9ea80bf
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
});
|
||||
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user