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 下令。