# Caddy v2 反向代理：自動 HTTPS（Let's Encrypt）+ 跨來源寫入擋截 + SSE 不緩衝。
# {$SITE_DOMAIN}、{$ACME_EMAIL} 由 docker-compose.prod.yml 的 caddy 環境變數帶入
# （值來自 /opt/live-translate/compose.env，由 deploy.sh 從 deploy/site.env 產生）。

{
  # Let's Encrypt 帳號 email（憑證到期通知會寄到這裡）
  email {$ACME_EMAIL}
}

{$SITE_DOMAIN} {
  # --- 原始碼下載（AGPL §13）：/source/ ---
  # 放在最前面，且只接 GET/HEAD：不會被下面兩個擋截 matcher 影響
  # （跨來源擋截只看 POST/PUT/PATCH/DELETE；管理路徑清單裡沒有 /source）。
  # 掛載進來的 /srv/source 只有「目前部署版本」的 tar.gz（由 deploy.sh 管理，回滾時一併切換）。
  # 用 handle + uri strip_prefix（等同 handle_path），確保與其他 handle 互斥。
  redir /source /source/ permanent
  @source_get {
    method GET HEAD
    path /source/*
  }
  handle @source_get {
    uri strip_prefix /source
    root * /srv/source
    @tgz path *.tar.gz
    header @tgz Content-Disposition attachment
    file_server browse
  }

  # --- 跨來源寫入擋截（公司規範：同網域 *.apps.df-recycle.com 的鄰居網站 CSRF 防護）---
  # 同時符合下列三項才擋（matcher 區塊內多個條件是 AND）：
  #   1. 方法會改資料：POST / PUT / PATCH / DELETE
  #   2. 請求「有」Origin 標頭（header Origin * 表示標頭存在，值不拘）
  #   3. Origin 不等於本站網址
  # 沒有 Origin 的請求（程式化 client 用 API key）不符合條件 2，放行。
  # WebSocket 升級是 GET，不符合條件 1，不受影響。
  @cross_origin_write {
    method POST PUT PATCH DELETE
    header Origin *
    not header Origin https://{$SITE_DOMAIN}
  }
  handle @cross_origin_write {
    respond "Cross-origin write blocked" 403
  }

  # --- 管理路徑只限公司網路 ---
  # 同時符合下列三項才擋（AND）：
  #   1. 來源 IP 不在 {$ADMIN_ALLOWED_CIDRS}
  #   2. 路徑屬於管理面（登入、控制台、Socket.IO、全部 /api/*）
  #   3. 不是「觀眾串流」GET /api/session/<sid>/stream（觀眾頁 rt/yt 唯一呼叫的 API，SSE，必須公開）
  # 公開面不列在 path 裡所以不受限：/ /rt/* /yt/* /rt-demo /static/* /favicon.ico /download/*
  #
  # 前提：本 VM 直接對外、前面沒有負載平衡器，所以 remote_ip（TCP 來源位址）就是真實 client IP。
  #       日後前面若加 LB／CDN，必須改用 client_ip 並設定 trusted_proxies，否則所有人都會變成 LB 的 IP。
  # {$ADMIN_ALLOWED_CIDRS} 是 Caddyfile 解析前的文字替換：值是空白分隔的 CIDR，
  #       例 "203.0.113.10/32 198.51.100.0/24"，會原樣展開成 remote_ip 的多個參數（清單不可為空）。
  @admin_outside {
    not remote_ip {$ADMIN_ALLOWED_CIDRS}
    path /login /logout /auth/* /panel/* /edit/* /dashboard /dashboard/* /user-dashboard /heartbeat/* /socket.io/* /api/*
    not {
      method GET
      path_regexp ^/api/session/[^/]+/stream$
    }
  }
  # handle 區塊依書寫順序比對、命中第一個就停：/source 下載 -> 跨來源擋截 -> 管理路徑擋截 -> 反向代理
  handle @admin_outside {
    respond "Management access is limited to the company network" 403
  }

  # --- 其餘請求交給後端 ---
  handle {
    reverse_proxy backend:5000 {
      # SSE 觀眾串流：立即轉送每個資料塊，不緩衝（-1 = 不延遲 flush）
      flush_interval -1
    }
  }
}
