This commit is contained in:
2026-05-24 16:38:23 -04:00
parent cfe3b29c98
commit 421b3d5922
2 changed files with 81 additions and 227 deletions
-200
View File
@@ -1,200 +0,0 @@
# Caddyfile — Phase 9 §9.2 proposed form.
#
# Three changes vs. /etc/caddy/Caddyfile (2026-05-24):
# 1. @static matcher now explicitly excludes /uploads/* — without this, an
# uploaded *.jpg matched @static before @gated and slipped past the
# forward_auth gate, hitting the SPA build root and returning a public 404.
# 2. The security_headers snippet no longer sets Access-Control-Allow-* —
# the upstreams' shared/cors/policy.js is the single source of truth for
# CORS responses (Phase 6.6).
# 3. New @cors_preflight handler punts OPTIONS preflights past forward_auth
# so the upstream's CORS middleware can answer them (preflights have no
# Authorization header, so they 401'd at the gate previously).
#
# Apply via the staged-cutover convention in Deviation #8:
# scp this file to netcup:/home/matt/Caddyfile.new
# curl --silent -X POST -H "Content-Type: text/caddyfile" \
# --data-binary @/home/matt/Caddyfile.new http://localhost:2020/load
# # ...smoke-test, then persist:
# sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak.YYYY-MM-DD
# sudo cp /home/matt/Caddyfile.new /etc/caddy/Caddyfile
{
admin :2020
}
(security_headers) {
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
X-XSS-Protection "1; mode=block"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
Referrer-Policy "strict-origin-when-cross-origin"
# Phase 9 §9.2: CORS headers removed. Upstreams set ACAO conditionally
# via shared/cors/policy.js; Caddy stamping `*` here was overriding it.
}
}
files.acot.site {
reverse_proxy localhost:8060
}
pbx.acot.site {
@ws path /ws
handle @ws {
reverse_proxy 127.0.0.1:8088
}
handle {
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_down Location http://127.0.0.1:8080 https://pbx.acot.site
header_down Location http://pbx.acot.site:8080 https://pbx.acot.site
}
}
}
turn.acot.site {
respond 404
}
freescout.acot.site {
root * /var/www/freescout/public
encode gzip
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
# Deny access to dotfiles
@dotfiles path */.*
respond @dotfiles 403
}
phone.acot.site {
reverse_proxy 127.0.0.1:3020
encode gzip
}
crafty.acot.site {
reverse_proxy localhost:8443 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-Port 443
header_up Host {host}
transport http {
tls_insecure_skip_verify
}
}
}
cronicle.acot.site {
reverse_proxy localhost:3100 {
header_up X-Forwarded-Proto https
}
}
inventory.acot.site, acot.site {
redir https://tools.acherryontop.com{uri} permanent
}
tools.acherryontop.com {
import security_headers
# Public: login endpoint
handle /auth-inv/* {
uri strip_prefix /auth-inv
reverse_proxy localhost:3011
}
# Phase 9 §9.2 — CORS preflight bypass.
# Browsers send OPTIONS preflights without Authorization, so forward_auth
# 401s them. Route preflights straight to the upstream which runs
# shared/cors/policy.js and answers correctly. Must come before @static
# and @gated so OPTIONS to *.jpg paths under /uploads/* also work if any
# frontend ever XHRs an upload URL.
@cors_preflight {
method OPTIONS
header Access-Control-Request-Method *
}
handle @cors_preflight {
handle /api/klaviyo/* {
reverse_proxy localhost:3015
}
handle /api/meta/* {
reverse_proxy localhost:3015
}
handle /api/dashboard-analytics/* {
reverse_proxy localhost:3015
}
handle /api/typeform/* {
reverse_proxy localhost:3015
}
handle /api/acot/* {
reverse_proxy localhost:3012
}
handle /chat-api/* {
uri strip_prefix /chat-api
reverse_proxy localhost:3014
}
handle /api/* {
reverse_proxy localhost:3010
}
}
# Public: static frontend assets (long-cache).
# Phase 9 §9.2: `not path /uploads/*` ensures uploaded images never get
# served from the SPA build root — they must go through @gated below.
@static {
path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2
not path /uploads/*
}
handle @static {
header Cache-Control "public, max-age=2592000"
root * /var/www/inventory/frontend/build
file_server
}
# ----- Authenticated zone -----
# Phase 6.1: forward_auth subrequest to auth-server:/verify. 2xx → proceeds.
# 401/403 → Caddy returns auth-server response to client; backend never sees it.
@gated path /api/* /chat-api/* /uploads/*
handle @gated {
forward_auth localhost:3011 {
uri /verify
copy_headers Authorization
}
# Phase 6.7: /uploads/* now behind the gate (was a public file_server before).
# Phase 9 §9.2 closes the static-matcher bypass that made this ineffective.
handle /uploads/* {
root * /var/www/inventory
file_server
}
# Phase 4: merged dashboard-server (klaviyo + meta + google + typeform).
handle /api/klaviyo/* {
reverse_proxy localhost:3015
}
handle /api/meta/* {
reverse_proxy localhost:3015
}
handle /api/dashboard-analytics/* {
reverse_proxy localhost:3015
}
handle /api/typeform/* {
reverse_proxy localhost:3015
}
# ACOT
handle /api/acot/* {
reverse_proxy localhost:3012
}
# Chat (Phase 9 §9.1 — chat-server now has its own authenticate() too)
handle /chat-api/* {
uri strip_prefix /chat-api
reverse_proxy localhost:3014
}
# Catch-all: inventory-server
handle /api/* {
reverse_proxy localhost:3010
}
}
# Out-of-band probes (unauthenticated)
handle /health {
reverse_proxy localhost:3010
}
# SPA fallback (public assets)
handle {
root * /var/www/inventory/frontend/build
try_files {path} /index.html
file_server
encode gzip
}
handle_errors {
respond "{err.status_code} {err.status_text}"
}
}