Auth pillar 從 OAuth 2.0 resource server 改成 pre-shared API key (visionA ↔ converter 1:1 internal trust)。新增 GET /api/v1/jobs/:id/result streaming endpoint 給 visionA backend 中轉 NEF 下載。 Phase A(auth 切換): - 新增 apiKeyMiddleware(constant-time compare、tokenFingerprint、4 audit events) - 砍 OAuth middleware + JWKS(保留 oauthClient 供 promote → FAA 使用) - 4 個 endpoint 換掛 requireApiKey - 加 TRUST_PROXY env + Express trust proxy 設定(forensic source_ip) Phase B(/result endpoint): - streaming NEF download with 5min timeout + concurrent cap 10 - Two-tier rate limit(burst 5/10s + sustained 20/min) - Bandwidth quota(1 GB/hr + 6 GB/24hr)by token_fingerprint - Range header silently ignored + Accept-Ranges: none - filename quote-escape + RFC 5987 fallback + sanitize - 8 個 /result audit events(forensic 完整) 設計演進記錄:docs/TODO-visionA-integration-v2.md(5/2 OAuth → 5/16 API key → 5/16 download via converter;對應 visionA repo ADR-015/016) Tests: 597 → 666 (+69)、29 suites all pass Security: APPROVE WITH CONDITIONS(單 instance 部署、6 新 env、24hr 監控) npm audit: 3 vuln → 0(transitive AWS SDK xml chain) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
TDD 索引 — Kneron Model Converter 對外 API
作者:Architect Agent
狀態:Draft(Phase 0.8b 重寫 + 模組化)
最後更新:2026-05-16
auth 設計演進:本 TDD 反映 Phase 0.8b 拍板後的「目標狀態」。完整歷史見 visionA repo
docs/autoflow/04-architecture/adr/adr-015-server-to-server-api-key.mdv2.1 +adr-016-download-via-converter.mdv1.0。配套:
design-doc.md(架構決策)、../02-prd/PRD.md(需求)、../03-design/design-review.md(UX 回饋)。
變更歷程
| 日期 | 變更 | 作者 |
|---|---|---|
| 2026-04-25 | 初版 Draft 1.0(OAuth resource server + promote) | Architect Agent |
| 2026-04-25 | Multipart 上傳路徑改 visionA → converter 直傳;移除 FAA GET/HEAD | Architect Agent |
| 2026-05-16 | Phase 0.8b 重寫:visionA → converter 改 API key;新增 /result endpoint;OAuth resource server 章節砍除;模組化拆分為索引 + 子檔案 |
Architect Agent |
1. 文件結構
本 TDD 在 Phase 0.8b 重寫時拆分為模組化結構:
| 檔案 | 內容 | 目標讀者 |
|---|---|---|
TDD.md(本檔,索引) |
各章節摘要 + 子檔案連結 | 全部 |
auth.md |
API key middleware 設計 + 砍除 OAuth resource server 清單 + 保留 OAuth client | Backend |
api/api-jobs.md |
POST/GET /jobs、GET /jobs/:id 規格 |
Backend、Reviewer、Testing |
api/api-promote.md |
POST /jobs/:id/promote 規格 |
Backend、Reviewer、Testing |
api/api-result.md |
新增 GET /jobs/:id/result 規格 |
Backend、Reviewer、Testing |
database.md |
Redis schema + 索引 + Lua script | Backend |
infra.md |
Nginx / docker-compose / .env 變動 + 部署順序 | Backend、DevOps |
performance.md |
SLO + 延遲預算 + 負載測試 | Backend、Testing |
observability.md |
Log 格式 + 敏感資料保護 + 告警 | Backend |
security.md |
Trust boundary + Input validation + Auth security | 全部 |
design-doc.md |
架構決策 + ADR | 全部 |
2. Phase 0.8b 改動摘要
2.1 對外 auth 改 API key
- 砍
auth/middleware.js(OAuth resource server)+auth/jwks.js - 加
auth/apiKeyMiddleware.js - 4 個既有 endpoint 改掛
requireApiKey() - 新加
/resultendpoint 也用requireApiKey()
詳見 auth.md §1 + §3(砍除清單)。
2.2 新增 /result endpoint
GET /api/v1/jobs/:id/result- Streaming proxy NEF from MinIO → caller
- 4 種 4xx + 2 種 5xx 情境
- 雙路徑 NEF key 解析(新格式 + 舊格式向後相容)
- 2026-05-17 補充:rate limit(60 req/min,獨立 bucket)、Range header 防護(silently ignore)、audit log 8 個 action、Backend
source_filename寫入 acceptance criteria(§9-§14)
詳見 api/api-result.md。
2.3 保留不動
- Promote 流程(converter → FAA 仍走 OAuth client_credentials)
- Redis schema(除確認
source_filename欄位存在) - Worker、MinIO bucket、Nginx 結構
詳見 auth.md §2 + api/api-promote.md。
2.4 Config 變動
- 移除:
MEMBER_CENTER_ISSUER/MEMBER_CENTER_JWKS_URL/KNERON_CONVERTER_AUDIENCE/JWKS_*/JWT_CLOCK_TOLERANCE_SEC - 新增:
CONVERTER_API_KEY - 保留:
MEMBER_CENTER_TOKEN_URL/KNERON_CONVERTER_CLIENT_*/FILE_ACCESS_AGENT_*/OAUTH_*
詳見 infra.md §3。
3. 系統概述
3.1 角色
- Converter(本專案):Node.js Task Scheduler + Python Worker
- visionA-backend:Go 服務,Converter 對外 API 的唯一 caller
- Member Center(MC):OAuth authorization server — Phase 0.8b 後只給 Converter → FAA promote 用
- File Access Agent(FAA):NAS 邊界檔案閘道,single-tenant per instance
3.2 API 端點清單(Phase 0.8b 後)
| 方法 | 路徑 | Auth | 說明 | 規格 |
|---|---|---|---|---|
| GET | /health |
— | 健康檢查 | api/api-jobs.md §3 |
| POST | /api/v1/jobs |
API key | 建立 job | api/api-jobs.md §4 |
| GET | /api/v1/jobs |
API key | 列表 / Recovery | api/api-jobs.md §6 |
| GET | /api/v1/jobs/:id |
API key | 單一 job 狀態 | api/api-jobs.md §5 |
| POST | /api/v1/jobs/:id/promote |
API key | 搬檔到 FAA | api/api-promote.md |
| GET | /api/v1/jobs/:id/result |
API key | NEW stream NEF | api/api-result.md |
| POST | /api/v1/jobs/:id/download-tokens |
API key | Phase 2,回 501 | api/api-jobs.md §7 |
| DELETE | /api/v1/jobs/:id |
API key | Phase 2,回 501 | api/api-jobs.md §7 |
3.3 既有路徑(Phase 0.8b 不動)
| 方法 | 路徑 | 用途 |
|---|---|---|
| POST | /jobs (multipart) |
Web UI 既有上傳 |
| GET | /jobs/:id |
Web UI 狀態查詢 |
| GET | /jobs/:id/events (SSE) |
Web UI 進度 push |
| GET | /jobs/:id/download/:filename |
Web UI 下載 |
| GET | /queues/stats |
內部監控 |
這些走 internal vhost,不對外、不加 auth。
4. 技術堆疊(不變)
| 層級 | 選擇 |
|---|---|
| 後端框架 | Node.js 18 + Express 4 |
| 認證(對外) | API key(crypto.timingSafeEqual,Phase 0.8b 新) |
| 認證(promote) | OAuth client_credentials(jose / 自寫 fetch) |
| 資料庫 | Redis 7 |
| 物件儲存 | MinIO(Converter Bucket) |
| Worker | Python 3.10+ |
| 反向代理 | Nginx |
| 測試 | Jest |
詳見 design-doc.md §3.5。
5. 專案結構
apps/task-scheduler/
├── server.js ← Entry
├── src/
│ ├── config.js ← 集中讀 env(Phase 0.8b 改)
│ ├── redis.js ← Redis client
│ ├── auth/
│ │ ├── apiKeyMiddleware.js ← 【新】Phase 0.8b
│ │ ├── oauthClient.js ← 【保留】promote 用
│ │ ├── middleware.js ← 【砍】OAuth resource server
│ │ └── jwks.js ← 【砍】
│ ├── fileAccessAgent/
│ │ ├── client.js ← FAA HTTP client(保留)
│ │ └── errors.js ← 錯誤翻譯(保留)
│ ├── routes/
│ │ ├── legacy.js ← 既有 /jobs/* 路由
│ │ └── v1/
│ │ ├── index.js ← v1 router 組裝(要改 wire result + 換 auth middleware)
│ │ ├── jobs.js ← POST/GET(要改換 requireApiKey)
│ │ ├── promote.js ← POST promote(要改換 requireApiKey)
│ │ └── result.js ← 【新】Phase 0.8b
│ ├── services/
│ │ ├── jobService.js ← Job CRUD
│ │ └── doneListener.js ← Worker done event
│ ├── middleware/
│ │ ├── errorHandler.js ← 統一錯誤
│ │ └── requestId.js
│ └── utils/
│ └── logger.js
├── docs/openapi.yaml ← 要改 security scheme(OAuth → bearer/api_key)
├── .env.example ← 要改(見 infra.md §4)
├── README.md ← 要改 auth 章節
└── package.json
6. 實作任務拆分(給 Backend)
按 Autoflow 增量式開發規範,每個任務 = 一個可獨立 review 的單位。
Phase A — API key middleware + auth 切換(取代 OAuth)
| # | 任務 | 依賴 | 預估 | 驗收標準 |
|---|---|---|---|---|
| A1 | 新建 src/auth/apiKeyMiddleware.js |
— | 1d | unit test 全過:happy path、missing header、wrong key、constant-time、destroy socket、env 未設定 fail-fast |
| A2 | 修 src/config.js:新增 converter.apiKey、移除 OAuth resource server 相關 env、保留 promote 相關 |
— | 0.5d | config.test.js 過;啟動時 CONVERTER_API_KEY 未設只 warn(不 throw);OAuth resource server env 移除後 server 仍能啟動 |
| A3 | 修 src/routes/v1/index.js / jobs.js / promote.js:requireAuth(scope) → requireApiKey() |
A1, A2 | 0.5d | 既有 integration test 全過(401 行為改成 API key 模式驗);server 啟動正常 |
| A4 | 砍 src/auth/middleware.js + src/auth/jwks.js + 相關 test |
A3 | 0.5d | git rm + test runner 沒 broken import;search code base 沒有 reference 殘留 |
| A5 | 修 .env.example、docs/openapi.yaml、README.md:移 OAuth resource server 段、加 CONVERTER_API_KEY |
A4 | 0.5d | docs lint 過;OpenAPI security scheme 改 bearer / api_key |
| A6 | Integration test:API key 驗證 4 個情境(happy / missing / wrong / 503) | A1-A5 | 1d | 全部過;既有 jobs / promote integration test 仍過 |
Phase A 總工時:~4d
Phase B — /result endpoint
| # | 任務 | 依賴 | 預估 | 驗收標準 |
|---|---|---|---|---|
| B1 | 確認 jobService.createJob 寫入 source_filename 欄位(檢查既有 code、補上若缺) |
— | 0.5d | unit test 過;既有 job record 結構不破壞 |
| B2 | 新建 src/routes/v1/result.js(含 extractNefObjectKey、buildFilename、stream handler) |
B1, A1 | 1.5d | unit test 過:filename 各情境、雙路徑 key 解析、stream error / client close handling |
| B3 | Wire /result 到 src/routes/v1/index.js(含 requireApiKey + per-client rate limiter) |
B2 | 0.5d | server 啟動 + route table 正確;mergeParams 取 :id 通 |
| B4 | Integration test:/result 8 個情境(200 happy / 401 / 404 job / 404 result / 409 / 410 expired / 410 minio miss / 502) |
B2, B3 | 1d | 全部過 |
Phase B 總工時:~3.5d
任務排程建議
順序執行 A → B(Backend 單人):
- A1 + A2 可平行
- A3 等 A1 + A2
- A4 等 A3
- A5 等 A4
- A6 等 A5(整體 verify)
- B1 + B2 可平行(B1 簡單,B2 是主要工作)
- B3 等 B2
- B4 等 B3
預估總工時:~7.5 工作日(單人)。若可雙人並行,A 和 B 可分工,壓到 ~5d。
與 visionA 端的 dependency
| Backend 任務狀態 | visionA 端可以做什麼 |
|---|---|
| Phase A 完成、deploy stage | visionA 可以打 stage converter 的既有 endpoint 驗 API key 流程 |
| Phase B 完成、deploy stage | visionA 可以打 /result endpoint 驗 streaming |
| Phase A + B 都 deploy 完 | e2e 驗證(visionA repo commit 9e29ebf 已 ready) |
7. 測試策略
詳見 performance.md §7 + 各 api/*.md 的 test 章節。
7.1 Unit test 覆蓋率目標
apiKeyMiddleware:100%(少量 code、必須全 cover)result.js:90%- 既有 OAuth-related 改動:維持 ≥ 85%
7.2 Integration test 必跑
- API key 4 情境(happy / missing / wrong / 503)
- 既有 jobs / promote 在 API key 模式下仍過
/result8 情境(見api/api-result.md§7.1)
7.3 Manual stage e2e(部署後)
- curl 驗:
/health、POST /jobs、GET /jobs/:id、POST /promote、GET /result - visionA 端 e2e:完整 upload → poll → promote → download
8. 安全注意事項
詳見 security.md。重點:
CONVERTER_API_KEY不進 git / log / Slackconstant-time compare(防 timing attack)- Sec C1 暫緩(
.envhistory rewrite + secret rotation 在 Phase 1 ready 後做、含 CONVERTER_API_KEY) - Trust boundary:visionA 一旦被 compromise 可冒充任意 user_id(接受、與 OAuth 模型一致)
9. 風險與待確認
| # | 風險 | 影響 | 行動 |
|---|---|---|---|
| R1 | CONVERTER_API_KEY rotation 流程未自動化 | 低 | Phase 1 接受手動 |
| R2 | /result 高並發 stream 壓力 |
低 | NEF 通常小、visionA 是唯一 caller、QPS 可控 |
| R3 | Sec C1 暫緩(.env 進 git history) | 中 | Phase 1 ready 收尾後 rewrite |
| R4 | NEF 7 天過期後 client 重新轉檔 | 低 | API spec 已定義 410,visionA 端處理 |
| R5 | Phase 0.8b 部署期間「OAuth → API key」短暫不可用 | 低 | 既有 stage OAuth 從未跑通、不會有 regression |
10. 後續步驟
- 本 TDD 索引 + 子檔案送 PM / Design 三方互審
- 使用者審核
- Backend Agent 依 §6 的任務拆分增量開發
- Reviewer 每個任務把關
- Testing 整合測試 + e2e
- DevOps 部署(converter 先 + visionA 後)
11. 變更記錄
| 日期 | 版本 | 變更 | 作者 |
|---|---|---|---|
| 2026-04-25 | Draft 1.0 | 初版,Phase 1 完整規格(單檔 1390 行) | Architect Agent |
| 2026-04-25 | Draft 1.1 | Multipart 上傳路徑改 | Architect Agent |
| 2026-05-16 | Draft 2.0 | Phase 0.8b 重寫:API key + /result + 模組化拆分為索引 + 8 個子檔案 | Architect Agent |
附註:本 TDD 從 1390 行單檔重組為 ~180 行索引 + 8 個子檔案。每個子檔案 < 500 行(單一職責),可獨立給 Backend / Reviewer / Testing 不同角色讀對應檔案、減少 context 負擔。