jim800121chen d8a9517c9d feat(task-scheduler): Phase 0.8b — API key auth + /result endpoint
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>
2026-05-17 22:47:28 +08:00

295 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TDD 索引 — Kneron Model Converter 對外 API
## 作者Architect Agent
## 狀態DraftPhase 0.8b 重寫 + 模組化)
## 最後更新2026-05-16
> **auth 設計演進**:本 TDD 反映 Phase 0.8b 拍板後的「目標狀態」。完整歷史見 visionA repo `docs/autoflow/04-architecture/adr/adr-015-server-to-server-api-key.md` v2.1 + `adr-016-download-via-converter.md` v1.0。
>
> **配套**`design-doc.md`(架構決策)、`../02-prd/PRD.md`(需求)、`../03-design/design-review.md`UX 回饋)。
---
## 變更歷程
| 日期 | 變更 | 作者 |
|------|------|------|
| 2026-04-25 | 初版 Draft 1.0OAuth 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` endpointOAuth 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()`
- 新加 `/result` endpoint 也用 `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 limit60 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 CenterMC**OAuth authorization server — Phase 0.8b 後**只**給 Converter → FAA promote 用
- **File Access AgentFAA**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_credentialsjose / 自寫 fetch|
| 資料庫 | Redis 7 |
| 物件儲存 | MinIOConverter Bucket |
| Worker | Python 3.10+ |
| 反向代理 | Nginx |
| 測試 | Jest |
詳見 `design-doc.md` §3.5。
---
## 5. 專案結構
```
apps/task-scheduler/
├── server.js ← Entry
├── src/
│ ├── config.js ← 集中讀 envPhase 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 schemeOAuth → 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不 throwOAuth 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 importsearch 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 testAPI 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 模式下仍過
- `/result` 8 情境(見 `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 / Slack
- `constant-time compare`(防 timing attack
- Sec C1 暫緩(`.env` history rewrite + secret rotation 在 Phase 1 ready 後做、含 CONVERTER_API_KEY
- Trust boundaryvisionA 一旦被 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 已定義 410visionA 端處理 |
| R5 | Phase 0.8b 部署期間「OAuth → API key」短暫不可用 | 低 | 既有 stage OAuth 從未跑通、不會有 regression |
---
## 10. 後續步驟
1. 本 TDD 索引 + 子檔案送 PM / Design 三方互審
2. 使用者審核
3. Backend Agent 依 §6 的任務拆分增量開發
4. Reviewer 每個任務把關
5. Testing 整合測試 + e2e
6. 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 負擔