# Hermes Auth Proxy — Caddy config
#
# Sits in front of the dream-hermes container. Gates LAN access by
# verifying the `dream-session` cookie against dashboard-api's
# /api/auth/verify-session endpoint — which validates the cookie's
# HMAC signature against DREAM_SESSION_SECRET (see session_signer.py).
#
# An earlier draft of this file only checked that the cookie HEADER
# was present (`header_regexp Cookie dream-session=[^;]+`). That's
# trivially bypassed — any LAN user can set `Cookie: dream-session=foo`
# in curl or devtools and slip past the gate. We now do real signature
# verification by delegating to dashboard-api via forward_auth.
#
# Flow:
#   1. Request arrives on :9120
#   2. /health and /favicon.ico — always answered (so health checks work)
#   3. /auth/* — static "you need an invite" assets (always served)
#   4. Everything else: forward_auth to dashboard-api/api/auth/verify-session
#      - 2xx → reverse-proxy to dream-hermes:9119
#               (Hermes's own X-Hermes-Session-Token check still runs)
#      - 401 → 303 redirect to /auth/required (the "ask the admin" page)
#
# This is auth GATING, not user identification. Once past the gate,
# all users share the same Hermes instance (same memories, skills,
# persona). See docs/HERMES-SSO.md for the explicit limitation.

{
	# Disable Caddy's auto-HTTPS — Dream Server's TLS is handled at the
	# Tailscale layer (PR-12) or by an explicit operator-side reverse
	# proxy. Inside the bridge network we serve plain HTTP.
	auto_https off
	# Don't expose Caddy's admin API. It's an attack surface we don't need.
	admin off
	# Log to stdout for `docker logs dream-hermes-proxy`.
	log {
		output stdout
		format console
		level INFO
	}
}

# Listen on the proxy's internal port. Compose maps this to the external
# HERMES_PROXY_PORT via the host port mapping.
:9120 {
	# Cheap health endpoint for Docker / dashboard probes. NEVER gates on
	# the cookie — health checks are anonymous by design.
	@health path /health
	handle @health {
		respond "ok" 200
	}

	@favicon path /favicon.ico
	handle @favicon {
		respond 204
	}

	# Static "you need an invite" page lives at /auth/required. Always
	# accessible; this is where unauthenticated requests get bounced.
	#
	# `templates` lets index.html substitute `{{env "HERMES_PROXY_AUTH_DOCS_URL"}}`
	# at response time, so operators can point recipients at a help doc /
	# admin-contact page via env var without editing the HTML. The page
	# only renders the "Need help?" row when the var is non-empty.
	@auth_static path /auth/required* /auth/static/*
	handle @auth_static {
		root * /srv/auth-required
		try_files {path} /index.html
		templates
		file_server
	}

	# Real session verification: delegate to dashboard-api's
	# /api/auth/verify-session. That endpoint reads the dream-session
	# cookie, verifies the HMAC signature against DREAM_SESSION_SECRET,
	# and returns 200 (valid) or 401 (missing/expired/bad-signature).
	#
	# Caddy's forward_auth issues a sub-request to the auth upstream
	# with the original Cookie header copied across, then:
	#   * 2xx → falls through to reverse_proxy below
	#   * non-2xx → the handle_response @denied block fires, sending the
	#               user to the auth-required page (303 — important so a
	#               no-cookie POST doesn't replay its body to /auth/required)
	#
	# DREAM_AUTH_UPSTREAM defaults to the in-network dashboard-api
	# hostname (which only the compose bridge can resolve); operators
	# can override via env if dashboard-api lives elsewhere.
	forward_auth {$DREAM_AUTH_UPSTREAM:dream-dashboard-api:3002} {
		uri /api/auth/verify-session
		copy_headers Cookie

		@denied status 401 403
		handle_response @denied {
			redir /auth/required 303
		}
	}

	# Forward everything to Hermes. reverse_proxy handles streaming
	# responses (SSE) and WebSocket upgrades natively — Caddy's
	# upstream-handling is the reason we picked it over rolling our
	# own FastAPI proxy.
	reverse_proxy {$HERMES_PROXY_UPSTREAM:dream-hermes:9119} {
		# Pass-through with sane defaults. Caddy's defaults are
		# correct for SSE + WS; we don't need to tweak buffering.
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-Host {host}
		# Add a marker the operator can grep for in Hermes's logs
		# if they're debugging a "did this request come via the
		# proxy or directly?" question.
		header_up X-Dream-Auth-Proxy "1"
	}
}
