# FR-048 Phase 4b 交接 prompt（軸⑤ signed-token：瀏覽器原生 GET 五件）

> 下面整段是給執行 session 的 prompt。與 4a（capability）檔案面不重疊，但不要同時跑（同一 working tree）。
> **本波跨 BE＋FE 兩 repo**（FE：`~/Projects/Billows/Audit-Manager/compliance-manager-fe/`，先讀其 CLAUDE.md；各 repo 分開 commit）。

---

【接手主題】FR-048 Phase 4b — 建 `common/authz/signed_token.py`（軸⑤ 短期簽章憑證），收掉五件瀏覽器原生 GET 免 bearer 端點

必讀：
1. `docs/analysis/2026-07-07-unified-auth-guard-design.md` **§6（機制設計全文：發放/驗證/TTL/自我限定，照它實作）**
2. 矩陣列：A 族 2 條（otp-qrcode、framework OSCAL download）＋ D14（file download/pdf-preview）＋ §7.3/§7.4 轉列的 survey / task-survey import-template
3. `docs/claude/frontend-overview.md`（FE 慣例）；FE 前科：ffa01a78（盲加 JWT 打爆 window.open 下載）

【branch】BE `fix/v1.8.0-bugs`；FE 用它當下 branch（不對停下問 user）。禁止切 branch。不動 jedi-* 套件。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【1. `common/authz/signed_token.py`（機制本體，照設計 §6）】

- `itsdangerous.URLSafeTimedSerializer`（已在依賴 2.2.0），key 自 `JWT_SECRET` 派生、**scope 當 salt**；payload `{scope, resource_uid, user_id}`；TTL 預設 **120 秒**。
- `issue_signed_token(scope, resource_uid)` — 由帶 bearer 的發放端點呼叫，user_id 取 `get_user_context()`。
- `@signed_token_required(scope=...)` route decorator — 驗簽＋scope＋TTL（過期/偽造 → 401，新增 error code 一枚：`error_code.py` TOKEN 族或 `AUTH_401` 序號續編，遵守碼規範）＋把 payload user_id 設進最小 context；**同時把 payload 交給 view（供 resource_uid 比對）**。
- 單測：正常過／過期 401／scope 不符 401／偽造 401／resource_uid 綁定。

【2. 五件逐一（每件都是「發 token 端點＋資源端點掛 decorator＋FE 改呼叫」三步）】

| # | 端點 | 特殊要求 |
|---|------|---------|
| 1 | `GET /otp-qrcode/<uid>`（user-profile#3） | **自我限定**：發放時 `resource_uid=發放者本人 uid`；驗證端比對 path uid == payload resource_uid → 只能拿自己的 QR（修掉「知 uid 拿他人 TOTP」的原洞，不只加認證）。FE：`UserProfileForm.vue` `<img :src>` 先打發放端點換 token 再組 src |
| 2 | `GET /oscal-framework-version/download/oscal/<uid>`（compliance-framework-manage#5，ffa01a78 前科件） | 發放端點掛 `@jwt_required`（登入即可讀，與 FR-042 政策一致）、token 綁 framework_version uid。FE：window.open 前先換 token 帶 `?st=` |
| 3 | `GET /file/download/<uid>`＋`/file/pdf-preview/<uid>`（D14 新發現，**影響面最廣**） | **先盤 FE 全部 call site**（grep api.js＋全 repo 用點）：axios blob 的呼叫方**不用動**（可同時掛 `@jwt_required` 相容雙軌——`st` 參數與 bearer 二擇一驗）；原生呼叫（window.open/`<img>`/`<iframe>`）逐個改走 token。**雙軌驗證設計**：decorator 先驗 `?st=`，無 st 時 fallback 驗 bearer（都無 → 401），避免一刀切改壞 axios 呼叫方 |
| 4 | `GET /survey/import-template`（window.location **LIVE**：BatchImportDialog） | token 綁 scope 即可（靜態範本無 resource）；FE 改先換 token |
| 5 | `GET /task-survey/import-template/<task_uid>`（唯一 consumer 在 `_archived`） | **先確認無 live consumer**：若確認 dead → 直接掛 `@jwt_required()` 即可（比照矩陣處置原則，不必上 token）；有 live 才走 token |

【3. e2e 回歸（設計 §6 明定必附）】

前科件（#1 #2）在 `~/Projects/Billows/Audit-Manager/compliance-manager-test/` 補 e2e：登入 → 個人資料頁 QR 圖有載出、框架管理頁 OSCAL 下載成功。做不完至少给出手測 checklist 逐步驟（含「登出狀態直接打資源 URL → 401」的負向案）。測試專案慣例讀其 CLAUDE.md。

【4. 驗證＋收尾】

- BE：單測＋baseline worktree 集合 diff（新增失敗 0）；**token 過期行為實測**（發 token → 等 TTL 過 → 401）。
- FE：五件相關頁面手開驗證（QR 顯示、下載成功、批次匯入範本下載）。
- 矩陣 A 族/D14/軸⑤ 各列銷帳＋§8 落地紀錄；BE/FE 各自分批 commit（顯式 add、禁 -am、不 push）。
- 回報必含：#3 的 FE call site 盤點結論（哪些 axios 不動、哪些原生改 token）、#5 的 dead/live 判定。收尾動作等 user 下令。
