對齊 ADR-015:visionA backend 從 OAuth client_credentials 改 pre-shared API key 服務間認證。Phase 0.8 stage e2e 撞 4 個 blocker(MC scope 沒註冊 / converter image 舊版 / converter 缺 env / FAA 不確定)後,使用者拍板 1:1 internal trust 用 OAuth 過度設計,改 API key。
實作(5 個增量 task,T1-T5 全綠 + Reviewer 5 輪 + final cross-task review):
T1 config + env:
- ConversionConfig 新增 ConverterAPIKey / FAAAPIKey 欄位
- Enabled() 改判定 4 欄位齊全(含兩個 API key)
- .env*.example 移除 OIDC service client / OIDC tenant / FAA delegated TTL env、新增 API key env
- TenantID / DelegatedTTLSeconds T1 暫留、T5 整批清
T2 client 改造:
- converter_client / faa_client 移除 MCTokenClient 依賴
- 直接讀 cfg.Conversion.{Converter,FAA}APIKey、set Authorization: Bearer <key>
- NewConverterClient / NewFAAClient APIKey 為空時 panic(fail-fast,對齊 ADR-015 §3.5.3 #1)
- 新增 ErrConverterAuthFailed / ErrFAAAuthFailed sentinel
- 對外 mask 成 converter_unavailable / faa_unavailable(不洩漏 401 細節,對齊 ADR-015 §3.5.3 #3)
T3 砍 mc_token_client:
- mc_token_client.go (624 行) + mc_token_client_test.go (864 行) 整檔砍
- 砍 5 個僅 mc_token_client 用的 sentinel(ErrServiceClientUnauthorized / ErrMCTokenUnavailable / ErrIDPMisconfigured / ErrIDPUnavailable / ErrDownloadTokenFailed)
- helper(truncate / silentLogger)搬到 util.go / testing_helpers_test.go
T4 flow + handler stream proxy:
- Service interface DownloadRedirectURL → DownloadStream(ctx) (io.ReadCloser, *DownloadMetadata, error)
- flow.DownloadStream 用 faa.GetFile 直接 stream NEF binary(取代 MC delegated token + 302)
- handler conversionDownloadHandler 改 io.Copy + Content-Type/Disposition/Cache-Control header
- 新增 sanitizeDownloadFilename helper 防 HTTP header injection
- 跨 package handler test (conversion_test.go) 改測 ErrFAAUnavailable + 補 *_AuthFailed 對稱 test
T5 wire 切換 + cleanup:
- main.go 砍 mcTokenClient wire、改 APIKey 注入、startup log 用 *_api_key_set boolean(不印 key)
- ConverterClient/FAAClient struct Tokens 欄位移除
- mc_token_stub.go (T4 過渡期) 整檔砍
- ConversionConfig TenantID / DelegatedTTLSeconds 欄位移除
- e2e_test.go TestConversionE2E_Download302Redirect 改寫為 TestConversionE2E_DownloadStream
- 補 MaxDownloadStreamBytes = 1 GiB size cap(io.CopyN,T4 reviewer Minor M-1)
- escapeObjectKeyPath Phase 1+ 預留 godoc + //nolint:unused(T4 reviewer Minor M-2)
- conversion.md §4.1 line 502 filename 來源描述歧義修訂(T4 reviewer Minor M-3,由 architect 處理)
- conversion_e2e_test.go 檔頭 docstring 更新(final reviewer Minor #1)
驗證:
- go build ./... exit 0
- go test -race -count=3 ./... 17 packages 全綠
- ADR-015 §6 砍除清單 100% cover(reviewer 獨立 grep 確認 MC chain / TenantID / DelegatedTTLSeconds 全清)
- ADR-015 §3.5.3 部署檢查清單 visionA 範圍 4/4 達成(fail-fast / mask / 不印 token / placeholder env)
不動:
- OIDCConfig.ServiceClientID/Secret 欄位保留(使用者拍板 backward compat)
- user login OIDC 完全不動
下一步:
- 步驟 4 — converter scheduler middleware 改 API key(jimchen 跨 repo,ADR-015 §3.5.1 Go snippet)
- 步驟 5 — FAA middleware 改 API key(warrenchen 跨 repo,ADR-015 §3.5.2 C# snippet)
- 步驟 6 — stage redeploy + e2e 完整測試
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
197 lines
8.5 KiB
Plaintext
197 lines
8.5 KiB
Plaintext
# visionA-backend 環境變數範本
|
||
#
|
||
# 使用方式:
|
||
# cp .env.example .env
|
||
# # 視情況修改 .env 內的值(尤其 VISIONA_STORAGE_SIGNING_SECRET 與 VISIONA_PAIRING_TOKEN)
|
||
#
|
||
# ⚠️ 不要把 .env commit 進 git(已在 .gitignore 中排除)
|
||
# 相關文件:
|
||
# - .autoflow/04-architecture/build-deploy.md §9(變數對照表)
|
||
# - internal/config/config.go(每個欄位的定義)
|
||
|
||
# ============================================================
|
||
# 共用
|
||
# ============================================================
|
||
|
||
# 日誌等級:debug / info / warn / error
|
||
VISIONA_LOG_LEVEL=info
|
||
|
||
|
||
# ============================================================
|
||
# api-server
|
||
# ============================================================
|
||
|
||
# 對前端的 REST / WebSocket port(對齊 local-tool 的 base URL 預設)
|
||
VISIONA_API_PORT=3721
|
||
|
||
# api-server 連 remote-proxy 的 internal HTTP base URL
|
||
# 本機 go run 時用 localhost;docker-compose 內部會被 compose 覆寫為 http://remote-proxy:3801
|
||
VISIONA_PROXY_INTERNAL_URL=http://localhost:3801
|
||
|
||
# Static user — Phase 0.7 security audit 後僅供 dev seed(VISIONA_SEED_DEMO_DATA=true)
|
||
# 與 unit test fixture 用;不再注入 api.Deps、stage/prod 留空無影響。
|
||
# 見 .autoflow/05-implementation/review/phase-0.7-security-audit.md C1。
|
||
VISIONA_STATIC_USER_ID=demo-user
|
||
|
||
# 啟動時 seed 示範資料(device + model + pairing token),方便前端 demo
|
||
VISIONA_SEED_DEMO_DATA=true
|
||
|
||
# CORS 白名單(逗號分隔)— 預設允許 frontend dev server(http://localhost:3000)
|
||
VISIONA_CORS_ALLOWED_ORIGINS=http://localhost:3000
|
||
|
||
# ============================================================
|
||
# OIDC(必填 — OB5 起 OIDC 是唯一認證路徑;A1 起支援 public PKCE-only client)
|
||
# ============================================================
|
||
# 必填欄位缺任何一項,main.go 啟動時會 fatal log 退出。
|
||
#
|
||
# 對應 Innovedus Member Center 的 OIDC client 設定:
|
||
# - 在 Member Center 註冊一個 OAuth client(confidential 或 public 皆可)
|
||
# - 取得 client_id(public client 沒有 client_secret)
|
||
# - 將 RedirectURL 加入 Member Center 的白名單
|
||
|
||
# Member Center 的 issuer(不帶結尾斜線;MC 的 issuer 末尾斜線必要時請保留)
|
||
# dev: http://localhost:5050
|
||
# stage: https://stage-9527.innovedus.com:7850/
|
||
# prod: https://members.innovedus.com
|
||
VISIONA_OIDC_ISSUER_URL=http://localhost:5050
|
||
|
||
# 在 Member Center 註冊的 OAuth client_id
|
||
VISIONA_OIDC_CLIENT_ID=visiona-cloud
|
||
|
||
# Client secret(A1:選填 — public PKCE-only client 留空)
|
||
# - 有值 → confidential client mode(client_secret + PKCE 雙保險)
|
||
# - 留空 → public PKCE-only client mode(依靠 PKCE 防 code interception)
|
||
# ⚠️ 不可 commit;prod 用 Secrets Manager。Stage MC 配的 client `b8093fea...` 是 public,留空。
|
||
VISIONA_OIDC_CLIENT_SECRET=
|
||
|
||
# Backend callback URL — 必須與 Member Center 註冊值完全一致
|
||
# dev: http://localhost:3721/api/auth/callback
|
||
# stage: https://stage-9527.innovedus.com:9527/api/auth/callback
|
||
# prod: https://api.visiona.cloud/api/auth/callback
|
||
VISIONA_OIDC_REDIRECT_URL=http://localhost:3721/api/auth/callback
|
||
|
||
# Frontend base URL — callback 完成後 302 redirect 的目的地
|
||
# dev: http://localhost:3000
|
||
# stage: https://stage-9527.innovedus.com:9527
|
||
# prod: https://app.visiona.cloud
|
||
VISIONA_FRONTEND_URL=http://localhost:3000
|
||
|
||
# Phase 0.8b 移除:VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRET
|
||
# 服務間認證從 OAuth client_credentials 改為 pre-shared API key(見 ADR-015、conversion.md §3)。
|
||
# 兩個 service client env 不再讀取(OIDCConfig.ServiceClientID/Secret struct 欄位
|
||
# 為了 backward compat 暫保留、但 conversion 模組不再依賴)。
|
||
# 取代設定見下方 Phase 0.8 / 0.8b 區塊的 VISIONA_CONVERTER_API_KEY / VISIONA_FAA_API_KEY。
|
||
|
||
# Cookie HMAC 簽章 secret(⚠️ 至少 32 byte 隨機字串;prod 用 openssl rand -hex 32)
|
||
VISIONA_SESSION_SECRET=CHANGE_ME_TO_RANDOM_64_BYTES_in_production
|
||
|
||
# Cookie 設定(dev 預設 host-only / non-secure;prod 改 .visiona.cloud + Secure=true)
|
||
VISIONA_SESSION_COOKIE_NAME=visiona_session
|
||
VISIONA_SESSION_COOKIE_DOMAIN=
|
||
VISIONA_SESSION_COOKIE_SECURE=false
|
||
|
||
# Session TTL — 預設 7 天 absolute / 24h idle
|
||
VISIONA_SESSION_ABSOLUTE_TTL=168h
|
||
VISIONA_SESSION_IDLE_TTL=24h
|
||
|
||
# Relay 對外可達 URL(agent tunnel 用)— POST /api/pairing/exchange 會回給 agent。
|
||
# 雛形為空時會 fallback 到 wss://relay.visionA.cloud(placeholder)。
|
||
# 實機請設為實際可達的 WSS URL,例:wss://relay.visionA.cloud
|
||
VISIONA_RELAY_PUBLIC_URL=
|
||
|
||
|
||
# ============================================================
|
||
# remote-proxy
|
||
# ============================================================
|
||
|
||
# 對 local agent 的 WebSocket tunnel port
|
||
VISIONA_TUNNEL_PORT=3800
|
||
|
||
# 對 api-server 的 internal HTTP port(不對外暴露)
|
||
VISIONA_PROXY_INTERNAL_PORT=3801
|
||
|
||
|
||
# ============================================================
|
||
# Tunnel 心跳 / 掉線判定(對齊 tunnel.md §4.2)
|
||
# ============================================================
|
||
|
||
VISIONA_TUNNEL_HEARTBEAT_INTERVAL=10s
|
||
VISIONA_TUNNEL_IDLE_TIMEOUT=30s
|
||
|
||
|
||
# ============================================================
|
||
# Storage(LocalFS — Phase 0 雛形;Phase 1 會改 S3)
|
||
# ============================================================
|
||
|
||
# 儲存根目錄(容器內;docker-compose 已 mount 成 volume)
|
||
VISIONA_STORAGE_BACKEND=localfs
|
||
VISIONA_STORAGE_LOCALFS_ROOT=./data/storage
|
||
|
||
# 瀏覽器 / 上傳 client 看到的 presigned URL base
|
||
# 本機開發:http://localhost:3721/storage
|
||
# docker-compose demo:同上(透過 host port mapping)
|
||
VISIONA_STORAGE_BASE_URL=http://localhost:3721/storage
|
||
VISIONA_STORAGE_LOCALFS_BASE_URL=http://localhost:3721/storage
|
||
|
||
# HMAC 簽章 secret — 用於 LocalFS presigned URL 與(Phase 1)pairing token hash
|
||
# ⚠️ 生產環境必改(openssl rand -hex 32 產生 64 字元 hex)
|
||
VISIONA_STORAGE_SIGNING_SECRET=CHANGE_ME_IN_PRODUCTION_use_openssl_rand_hex_32
|
||
|
||
|
||
# ============================================================
|
||
# Model 上傳限制
|
||
# ============================================================
|
||
|
||
# 單檔上限(MB)— Phase 0 規範 100 MB(PRD §8.4)
|
||
VISIONA_MODEL_MAX_SIZE_MB=100
|
||
|
||
|
||
# ============================================================
|
||
# Pairing(local agent ↔ remote-proxy 配對)
|
||
# ============================================================
|
||
|
||
# 格式:vAc_ + 32 hex(見 security.md §1.3)
|
||
# 建議用:vAc_$(openssl rand -hex 16)
|
||
# 留空代表雛形 InMemoryPairingStore 會動態配發(前端呼叫 POST /api/pairing/token)
|
||
VISIONA_PAIRING_TOKEN=
|
||
|
||
|
||
# ============================================================
|
||
# Phase 0.8 / 0.8b — 轉檔功能整合(converter / FAA pre-shared API key)
|
||
# ============================================================
|
||
# 對齊 .autoflow/04-architecture/conversion.md §3 + ADR-015。
|
||
#
|
||
# Phase 0.8b 變更:服務間認證從 OAuth client_credentials 改為 pre-shared API key。
|
||
#
|
||
# 啟用判定:4 個欄位(ConverterBaseURL / FAABaseURL / ConverterAPIKey / FAAAPIKey)
|
||
# **全部非空**才視為啟用;任一缺 → 5 個 /api/conversion/* endpoint 不註冊 / 回 501。
|
||
#
|
||
# Phase 0.8b 移除(不再讀取):
|
||
# - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRET(OAuth client_credentials 機制取消)
|
||
# - VISIONA_OIDC_TENANT_ID(取消 tenant 概念,converter 端的 user_id 仍由 visionA 灌入)
|
||
# - VISIONA_FAA_DELEGATED_TTL_SECONDS(delegated download token 機制取消,改 server-side stream proxy)
|
||
|
||
# kneron_model_converter task-scheduler base URL
|
||
# dev/stage:http://192.168.0.130:9501
|
||
# prod:https://converter.visiona.cloud
|
||
VISIONA_CONVERTER_BASE_URL=
|
||
|
||
# File Access Agent base URL
|
||
# dev/stage:http://192.168.0.130:5081
|
||
# prod:https://faa.innovedus.com
|
||
VISIONA_FAA_BASE_URL=
|
||
|
||
# Pre-shared API key — visionA → converter 服務間認證(Phase 0.8b 新增;ADR-015 §3)
|
||
# 產生:openssl rand -hex 32(64 字元 hex)
|
||
# 與 converter 端 CONVERTER_API_KEY env 對齊(雙方獨立持有,嚴格分環境 dev / stage / prod)
|
||
# ⚠️ 不可 commit;prod 用 Secrets Manager / Vault;log 永遠不印此值全文
|
||
VISIONA_CONVERTER_API_KEY=
|
||
|
||
# Pre-shared API key — visionA → FAA 服務間認證(Phase 0.8b 新增;ADR-015 §3)
|
||
# 產生方式同上;與 FAA 端 FAA_API_KEY env 對齊(warrenchen 配置)
|
||
# 與 ConverterAPIKey **不共用**(每條 trust boundary 各自獨立,避免一處洩漏連坐)
|
||
VISIONA_FAA_API_KEY=
|
||
|
||
# 上傳模型檔大小上限(MB)— 與 converter 端 limit 對齊
|
||
VISIONA_CONVERTER_MAX_MODEL_SIZE_MB=500
|