visionA/docs/SMOKE-TEST.md
jim800121chen eb66a7287a feat(deploy): visionA Cloud dev / stage docker compose + Caddy/nginx + 部署腳本
新增雲端版部署設定(Phase 0.6 dev + Phase 0.7 stage 分兩套):

dev 環境(docker-compose.dev.yml):
- 5 service all-in-one(postgres + member-center + visionA-backend + frontend + Caddy)
- Caddy 自動 HTTPS for localhost
- .env.dev.example 範本(使用者拷出 .env.dev 後 docker compose up -d)
- Makefile dev-with-mc 9 個 target

stage 環境(docker-compose.stage.yml + docker/Dockerfile.stage):
- multi-stage build(node22 frontend + go1.26 backend × 2 + nginx-alpine runtime)
  最終 image 319 MB,含 nginx + nodejs + tini + bash
- entrypoint.stage.sh 4 process 共命運(nginx + api-server + remote-proxy +
  next.js standalone)用 wait -n + SIGTERM trap
- nginx.stage.conf:白名單 server_name stage-9527.innovedus.com + 444 default_server
  + /healthz 例外(127.0.0.0/8 only)+ /api/ 與 /storage/ 強制 no-store
  + /tunnel/connect WS upgrade + 100M body / 3600s timeout
- 對外 mapping 0.0.0.0:9527:80(公司 host nginx 在外層處理 HTTPS termination
  — Let's Encrypt stage-9527.innovedus.com 自動續簽)
- named volume visiona-data(不用 bind mount,因 stage docker daemon 在 host root
  無 mkdir 權限)

部署腳本(scripts/deploy-stage.sh):
- 仿 edge-ai-platform/scripts/deploy-docker.sh 早期 save/load 模式
- 為什麼不用 internal registry:公司 192.168.0.130:5000 開了 auth、無帳密
- 流程:buildx --load → docker save | gzip → DOCKER_HOST docker load → compose up
- 含 --rollback <tag> / --skip-build / --no-push / --skip-deploy 選項
- timestamp + git SHA tag 留 rollback 餘地

文件(docs/):
- DEV-SETUP.md:dev 環境一鍵起步驟
- SMOKE-TEST.md:手動煙測 checklist(OIDC flow / pairing / tunnel)
- STAGE-DEPLOY.md:stage 完整手冊(架構圖 / 環境前置 / 部署 step / rollback /
  7 種故障排除 / 緊急救回 POC)

.env.stage.example 對齊 backend A1 改造:
- VISIONA_OIDC_CLIENT_SECRET 留空(PKCE-only public client)
- VISIONA_OIDC_SERVICE_CLIENT_ID/_SECRET 留空(Phase 1 預留鉤子)
- 所有 secret 用 placeholder(CHANGE_ME_OPENSSL_RAND_HEX_32)

.dockerignore:避免 node_modules / .next / .git 等進 build context

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:22:44 +08:00

17 KiB
Raw Blame History

visionA Phase 0.6 OIDC 手動煙測 Checklist

適用對象Phase 0.6 OIDC + Member Center 接入完成後,使用者首次驗收。

預期時間:~30 分鐘(含 OAuth client 手動設定 ~10 分鐘)

上位文件:.autoflow/04-architecture/oidc-tdd.md.autoflow/04-architecture/adr/adr-010-oidc-bff.mddocs/DEV-SETUP.md

為什麼是手動而不是自動MC admin API 目前有兩個 bugpassword grant 缺 sub claim → 500、admin API 無法回傳 client_secret導致 OAuth client 註冊無法用 script 自動化。詳見本文「§故障排除 #1」與「§已知 limitation」。


用法

每個 checkbox 代表一個獨立可驗證的步驟,逐項打勾。 有 不通過的步驟 → 看「§故障排除」對應編號 → 修完再 retry → 才往下走。 全部 走完 = Phase 0.6 OIDC 接入驗收通過。


前置條件

  • Docker Desktop 已開(docker info 不噴錯)
  • visionA + member_center 已 git clone 平行擺放(ls -d ../member_center 看得到)
  • 已照 docs/DEV-SETUP.md §4 跑過 docker compose -f docker-compose.dev.yml --env-file .env.dev up -d --build
  • frontend 依賴已裝:cd visionA-frontend && pnpm install
  • frontend dev server 已開:cd visionA-frontend && pnpm dev,能看到 http://localhost:3000 顯示登入頁

階段 1基礎服務驗證5 分鐘)

驗證 5 個 docker service + frontend 都健在。

  • 1.1 docker compose -f docker-compose.dev.yml ps → postgres、member-center、member-center-web、visiona-proxy、visiona-api 都顯示 Up (healthy)member-center-init 顯示 Exited (0)
  • 1.2 curl -sS http://localhost:5050/.well-known/openid-configuration | head -c 200 → 回 200 + JSON內含 "issuer":"http://localhost:5050/"
  • 1.3 curl -sS http://localhost:5050/jwks → 回 200 + JSON"keys": [...]
  • 1.4 curl -sS http://localhost:3721/healthz → 回 200visiona-api 健康)
  • 1.5 curl -sS http://localhost:3800/healthz → 回 200visiona-proxy 健康)
  • 1.6http://localhost:3000 → 看到 visionA login 頁(含「使用您的 Innovedus 帳號登入」按鈕)
  • 1.7http://localhost:5060 → 看到 Member Center admin 後台登入頁

任一步驟失敗 → §故障排除 #1。


階段 2Member Center 設定10 分鐘)

⚠️ 因 MC admin API 兩個 bug此階段必須走 Web UI 手動操作。詳見 §故障排除 #1。

2.1 登入 MC admin

  • 2.1.1http://localhost:5060
  • 2.1.2.env.dev 中的 MC_ADMIN_EMAIL / MC_ADMIN_PASSWORD 登入(預設 admin@visiona.local / Admin12345!
  • 2.1.3 進到 admin dashboard 不噴錯

2.2 建 Tenant

  • 2.2.1 進「Tenants」頁
  • 2.2.2 點「Create」
  • 2.2.3 填入:
    • NamevisionA
    • Domainsvisiona.cloud, localhost
    • Statusactive
  • 2.2.4 送出 → 列表出現 visionA tenant

2.3 建 OAuth Client

  • 2.3.1 進「OAuth Clients」頁
  • 2.3.2 點「Create」
  • 2.3.3 填入:
    • TenantvisionA
    • NamevisionA Cloud
    • Client Typeconfidential
    • Usagewebhook_outbound(雛形 workaround見 §已知 limitation #3
    • Redirect URIshttp://localhost:3721/api/auth/callback
  • 2.3.4 送出
  • 2.3.5 複製 client_idGUID 格式,例 a1b2c3d4-...)→ 暫存
  • 2.3.6 複製 client_secret(如 UI 沒給,見 §故障排除 #1.b 的 fallback→ 暫存

2.4 建 Demo User

  • 2.4.1 進「Users」頁
  • 2.4.2 點「Create」
  • 2.4.3 填入:
    • Emaildemo@visiona.local
    • PasswordDemo12345!
    • NameDemo User(或自填)
    • TenantvisionA
  • 2.4.4 送出 → users 列表出現 demo user

2.5 寫回 .env.dev + 切到 OIDC mode

  • 2.5.1 編輯 .env.dev,填入 2.3.5 / 2.3.6 拿到的值:
    VISIONA_OIDC_CLIENT_ID=<client_id>
    VISIONA_OIDC_CLIENT_SECRET=<client_secret>
    VISIONA_AUTH_TYPE=oidc
    
  • 2.5.2 重啟 visiona-api
    docker compose -f docker-compose.dev.yml --env-file .env.dev up -d visiona-api
    
  • 2.5.3 看 log
    docker compose -f docker-compose.dev.yml logs visiona-api --tail 20
    
    • 看到 OIDC initialized issuer=http://localhost:5050/ client_id=<你的 guid>
    • 看到 api-server listening addr=0.0.0.0:3721
    • 沒有 panic / fatal

任一步驟失敗 → §故障排除 #2。


階段 3完整 Login Flow5 分鐘)

驗證 OAuth Authorization Code + PKCE 走完整 redirect chain。

  • 3.1 開瀏覽器(建議無痕視窗,避免舊 cookie 干擾)→ http://localhost:3000/login
  • 3.2 看到「使用您的 Innovedus 帳號登入」按鈕
  • 3.3 開 DevTools → Network tab保持「Preserve log」勾選這樣才看得到 redirect chain
  • 3.4 點登入按鈕 → 第一個 request 是 GET /api/auth/loginresponse 302
  • 3.5 跟隨 302 → 第二個 request 是 GET http://localhost:5050/oauth/authorize?...MC 顯示登入頁)
  • 3.6 確認 authorize URL 內含:
    • response_type=code
    • client_id=<你的 guid>
    • scope=openid email profile
    • state=<random>
    • code_challenge=<...> + code_challenge_method=S256
    • nonce=<random>
    • redirect_uri=http://localhost:3721/api/auth/callback
  • 3.7 DevTools → Application → Cookies → http://localhost:3721 → 應已有 visiona_pending_sidHttpOnly
  • 3.8 MC 登入頁輸入 demo@visiona.local / Demo12345! → 送出
  • 3.9(如有同意授權頁)→ 點「同意」
  • 3.10 跟隨 302 → GET http://localhost:3721/api/auth/callback?code=...&state=...
  • 3.11 跟隨 302 → 回到 http://localhost:3000dashboard / workspace 首頁)
  • 3.12 DevTools → Cookies → http://localhost:3721visiona_session 出現HttpOnlyvisiona_pending_sid 已被清除
  • 3.13 Header 右上角顯示 demo user 的 email 或 namevisionA-frontend /account 應拿得到 user info

任一步驟失敗 → §故障排除 #3。


驗證 BFF Patternfrontend 只用 cookie所有 API 自動帶 session。

  • 4.1 DevTools → Network → 重整任意需登入頁面(如 /devices
  • 4.2 找到 GET /api/auth/me request
    • Request Headers 帶 Cookie: visiona_session=...
    • Response 200body 含 user_idOIDC sub不應是 demo-user)、emailnameexpires_at
  • 4.3http://localhost:3000/devices → 頁面正常載入API 拿到 cookie session
  • 4.4http://localhost:3000/models → 頁面正常載入
  • 4.5http://localhost:3000/account → 頁面正常載入並顯示 user 資訊
  • 4.6 開新 tab 直接用 curl替換 cookie 值):
    curl -sS -b "visiona_session=<從 DevTools 複製>" http://localhost:3721/api/auth/me
    
    → 200 + JSON
  • 4.7 curl 不帶 cookie
    curl -sSi http://localhost:3721/api/auth/me
    
    → 401middleware 擋下)

任一步驟失敗 → §故障排除 #4。


階段 5Pairing Token 綁 OIDC user5 分鐘)

驗證關鍵承諾:OIDC sub → UserContext → Pairing Token 的 user binding 正確。 這是 ADR-010 + oidc-tdd §9 的核心驗證點:取代 StaticAuth 後pairing 不會再綁到 demo-user

  • 5.1http://localhost:3000/devices/pair
  • 5.2 找到「產生 Pairing Token」按鈕點下去
  • 5.3 拿到 token格式類似 vAc_xxxxx
  • 5.4 DevTools → Network → 看 POST /api/pairing/token request
    • Request 帶 Cookie: visiona_session=...
    • Response 200 + JSON
  • 5.5 看 backend log 確認 user binding
    docker compose -f docker-compose.dev.yml logs visiona-api --tail 50 | grep -i pairing
    
    • 應看到「PairingStore Create userID=」之類 loguser_id 是 GUID 或 MC 給的 sub不是 demo-user
  • 5.6(可選,如有實作)查 storage/account 或對應頁面,確認該 token 列在「我的 pairing tokens」清單下
  • 5.7(可選)拿 token 餵給 local-tool agent → agent 連上 → 確認後端 log 看到 agent 連入時 binding 的 user_id 與 5.5 一致

任一步驟失敗 → §故障排除 #5。


階段 6登出 + 重登3 分鐘)

  • 6.1 Header → User menu → 點「登出」(或訪 /account 找登出按鈕)
  • 6.2 DevTools → Network → 看 POST /api/auth/logout → 204 No Content
  • 6.3 DevTools → Cookies → visiona_session 已被清除(或值改成空 + Max-Age=0
  • 6.4 自動 redirect 到 /login(或手動訪 /login 確認沒登入狀態)
  • 6.5http://localhost:3000/devices → 應 redirect 到 /login(或顯示「請先登入」),不應看到 devices 列表
  • 6.6 curl GET /api/auth/me 不帶 cookie → 401
  • 6.7 重新點登入按鈕 → 走完 §3 整套 flow → 又能進到 dashboard
  • 6.8 確認重新登入後 /api/auth/me 拿到的 user_id 跟之前一樣demo user 還是同一個 OIDC sub

任一步驟失敗 → §故障排除 #6。


階段 7跨頁 Refresh / 持久化2 分鐘)

驗證 cookie 7 天 + 24h idle 的 session 生命週期。

  • 7.1 登入狀態下重整瀏覽器 → 仍登入cookie 持久 + /api/auth/me 還能拿 user
  • 7.2 關掉瀏覽器 tab不要清 cookie)→ 開新 tab → 訪 http://localhost:3000 → 仍登入
  • 7.3 DevTools → Cookies → visiona_session 的 Max-Age 約 6048007 天)

故障排除

#1 階段 2 卡住MC admin 登入失敗、tenant/client 建不起來

a. admin 帳號登不進去

  • 確認 .env.devMC_ADMIN_EMAIL / MC_ADMIN_PASSWORD 與 docker compose 啟動時一致
  • docker compose logs member-center-init → 應有「admin user created」訊息
  • 完全重置:docker compose -f docker-compose.dev.yml down -v → 重新 up -d(會重跑 init

b. UI 沒顯示 client_secret

  • 已知 MC limitationadmin UI 建 confidential client 時可能不回傳 secret
  • 暫時 workaround:把 client type 改成 publicVISIONA_OIDC_CLIENT_SECRET 留空字串visionA-backend 應允許 empty secret 通過(雛形可接受)
  • 永久解MC 補 admin API 回傳 secret 的功能

c. 為什麼整個流程不能 script 自動化

  • MC password grant 在 Identity user 上有 bugmandatory subject claim was missing → 500
  • → 拿不到 admin token → 無法呼叫 admin API → 必須走 Web UI
  • 等 MC 修這兩個 bug 後OD2 可實作 seed scriptOT2 可改全自動 e2e

#2 visiona-api 看不到 "OIDC initialized" log或 startup 噴錯

log 關鍵字 原因 解法
discovery fetch failed MC 未起 / port 不對 確認 docker compose ps member-center healthycurl localhost:5050/.well-known/openid-configuration
issuer URL ... did not match issuer trailing slash 問題 .env.devVISIONA_OIDC_ISSUER_URL 必須結尾 /,例 http://localhost:5050/
IssuerURL invalid / ClientID required env 沒帶到 docker compose 命令必須加 --env-file .env.dev
auth type "oidc" but ClientID is CHANGE_ME 還沒填 client_id 完成階段 2.5.1

#3 階段 3 卡住:登入 redirect chain 失敗

症狀 原因 解法
點登入按鈕沒反應 frontend /api/auth/login 沒打到 backend 確認 VISIONA_FRONTEND_URL=http://localhost:3000、frontend NEXT_PUBLIC_API_BASE_URL=http://localhost:3721(或對應 env
302 到 MC 後 MC 噴 invalid_client client_id 不對 / tenant 沒 active 回 MC admin UI 檢查 client 是否屬於 active tenant
MC 登入後回 callback 噴 state_mismatch visiona_pending_sid cookie 沒帶過去 確認 backend Set-Cookie 沒設 Domain 限制;瀏覽器在 callback 時應該帶 cookieDevTools → Application → Cookies 看是否真有存進去)
callback 噴 pending_session_not_found pending session 過期10 分鐘)或 session store 重啟丟資料 重新點登入按鈕從頭來;雛形 in-memory store 重啟即清空
callback 噴 id_token_invalid id_token 驗簽失敗issuer / aud / nonce 對不上) 看 backend log detail常見JWKS cache 過時 → 重啟 visiona-api
callback 噴 token_exchange_failed client_secret 不對 / redirect_uri 不對 比對 .env.dev 與 MC admin UI 上的 client 設定
  • DevTools → Application → Cookies → 確認 visiona_session 真的存在 + 沒過期
  • DevTools → Network → 看 request 是否真的帶 Cookie: header
    • 如果沒帶 → frontend fetch 沒設 credentials: 'include',看 OF2 是否真的補上
  • backend log unauthorized 帶的 detail
    • cookie_invalid → cookie 簽章對不上 → 通常 VISIONA_SESSION_SECRET 變過導致舊 cookie 失效 → 清 cookie 重登
    • session_not_found → in-memory store 被重啟清掉 → 重登
    • session_expired → 真的過期了,重登

#5 階段 5Pairing token user_id 綁錯(綁到 demo-user 而非 OIDC sub

這是 OB5 / OF2 必須驗證的關鍵點

  • docker compose logs visiona-api | grep -i 'pairing\|userID\|user_id'
    • 如果 user_id 是 demo-user → OB5 沒拔乾淨 / VISIONA_AUTH_TYPE 還是 static → 確認階段 2.5.1 真的改成 oidc
    • 如果 user_id 是空 / unknown → middleware 沒注入 UserContext → 看 OB3 middleware 程式碼
    • 如果 user_id 是 GUIDOIDC sub 正確
  • 必要時開 issue「OB5 + OF2 完成後 pairing 仍綁到 demo-user需 hotfix」

#6 階段 6登出後仍能訪問

  • DevTools 確認 visiona_session cookie 真的被清掉
  • 如果 cookie 還在 → backend Set-Cookie: visiona_session=; Max-Age=0 沒設對
  • /api/auth/me 不帶 cookie 仍 200 → middleware bypass 了某些路徑 → 看 router 設定
  • frontend 沒 redirect 到 /login → middlewareNext.js沒檢查 session → 看 OF2/OF3 frontend route guard

已知 LimitationPhase 0.6 雛形)

# Limitation 影響 後續處理Phase 1
1 MC admin API 不接受 client_secret 輸入 / 不回傳 client_secret 必須走 MC Web UI 手動建 OAuth client MC 補 admin API
2 MC password grant 在 Identity user 缺 sub claim → 500 無法用 admin token script 自動化 seed MC TokenController 補 Subject claim mapping
3 MC 只支援 usage=webhook_outbound 帶 redirect_uris 命名語意不對visionA 借用) MC 新增 usage=web_app
4 visionA-backend in-memory session store 重啟即消失 雛形階段使用者重登 OK不適合多 instance Phase 1 換 Redis
5 host 上的 visiona-locallocal-tool佔 3721 與 docker compose 衝突 兩個只能擇一跑 預期docs 已說明
6 沒有 RP-initiated logout登出 visionA 不會把 MC session 也登出) 同 browser 下次點登入會自動跳過 MC 登入頁 Phase 1 補
7 沒有 refresh token rotation session 24h idle / 7d absolute 後必須重登 等 MC 開 refreshPhase 1 接

自動化進度

階段 自動化狀態 對應測試
階段 1基礎服務 docker-compose healthcheck 自動
階段 2OAuth client 設定) 手動MC bug 阻擋) Phase 1 待 MC 修 bug
階段 3-7Login flow / API / Pairing / Logout 用 fake OIDC 自動測OT1 internal/api/handlers/oidc_e2e_test.go17 packages 全綠 / 6 個 OIDC e2e cases
階段 3-7with 真 MC 手動(依賴階段 2 手動產 client Phase 1 TODO

Phase 1 TODO 清單

  1. MC team 修 password grant sub claim bug → unblock seed script
  2. MC team 補 admin API 接受 / 回傳 client_secret → unblock OAuth client 自動化
  3. MC team 新增 usage=web_app → 移除雛形 workaround
  4. visionA OD2make dev-seed 把 §2 全部 script 化
  5. visionA OT2 真 MC e2e把 OT1 fake OIDC 改 testcontainers 起真 MC跑 §3-§7 全自動
  6. visionA OB?:補 RP-initiated logout
  7. visionA OB?:補 refresh token等 MC 上 refresh
  8. visionA OB?in-memory session → RedisPhase 1 上 staging 必做)

通過條件Phase 0.6 OIDC 接入驗收)

  • 階段 1-7 全綠(每個 checkbox 都打勾)
  • 階段 5 確認 pairing user_id 是 OIDC sub 而非 demo-user最關鍵
  • 已知 limitation 都有 Phase 1 TODO 對應 issue / 任務

全部達成 → Phase 0.6 OIDC 接入完成 ,可進 Phase 1 規劃。