visionA/docs/autoflow/04-architecture/adr/adr-012-pending-session-shared-cookie.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類
共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git),
讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等
per-branch 筆記。

- 02-prd/        21 個檔(PRD、features、market-analysis 等)
- 03-design/     18 個檔(design-spec、wireframes、flows 等)
- 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等)
- 07-delivery/   3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup)

合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv,
但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
2026-05-04 16:55:55 +08:00

6.0 KiB
Raw Blame History

ADR-012Pending Session 與 Logged-in Session 共用同一個 Cookie

狀態

Accepted — 2026-04-21

上位文件

背景 (Context)

OIDC Authorization Code + PKCE flow 在 backend 端會產生兩種 server-side session 狀態:

  1. Pending sessionOIDC dance 進行中):/api/auth/login 階段建立,存放 PKCE code_verifier、CSRF state、OIDC nonce、return_to。生命週期短≤ 10 分鐘,從使用者點 Login 到 IdP 完成同意UserID 為空。
  2. Logged-in session(已登入):/api/auth/callback 完成後UserID 從 OIDC sub claim 填入pending 欄位清空。生命週期長(雛形 24hPhase 1 設計 7d

oidc-tdd.md §4.5 的原始設計示意了兩個獨立 cookie

  • visiona_pending_sid(短 TTL10 分鐘)
  • visiona_session(長 TTL登入後寫入

雛形實作OB2 / OB4為了減少 cookie 數量、簡化 handler 與 store 邏輯,改採合一:兩種 session 共用同一個 visiona_session cookie + 同一個 usersession.Store recordSession.UserID 是否為空來區分階段。

OB5 review2026-04-21將此偏離標為 Major-3「technical debt 而非漏洞」,建議 Phase 1 上線前評估是否還原 TDD 原設計。本 ADR 在 review round 2 後做出最終決定。

決策 (Decision)

保留合一設計pending 與 logged-in 共用同一個 visiona_session cookie還原 TDD §4.5 的兩個 cookie 設計。

配套防護機制

合一設計的潛在風險pending session 被當成 logged-in 用)由以下三道防線阻擋:

  1. AuthMiddleware 強制檢查 UserID == "" → 401 session_not_authenticated

    • internal/api/middleware.go AuthMiddleware 在拿到 session 後立刻判斷 UserID 是否為空
    • 空 → 401pending session 訪問 protected endpoint 一律拒絕)
    • 此檢查不可拿掉、不可放寬
    • 對應測試:TestOIDCMiddleware_Rejects_PendingSession
  2. Callback 完成時 rotate session IDFix-A1 / ADR-012 配套)

    • internal/api/oidc_auth.go callback handler 在驗 id_token 成功後立即呼叫 usersession.Manager.RotateSessionID
    • 新 session 從一個全新的 random ID 開始,舊 pending session ID 從 store 中刪除
    • 防護 session fixationOWASP ASVS V3.2.1
    • 對應測試:TestOIDCCallback_RotatesSessionID_PreventsFixationTestManager_RotateSessionID_HappyPath
  3. Pending state 在 callback 完成同一次 UpdateSession 中清空

    • OIDCState / OIDCNonce / OIDCCodeVerifier / Extra["return_to"] 在寫入 user info 的同一次 Update 中清掉
    • 確保 logged-in session 不殘留 pending 欄位

理由 (Rationale)

為什麼維持合一

  1. Cookie 數量少對 frontend 簡單frontend 只關心一個 cookie 是否存在,不需要區分 pending/logged-in 兩個 cookie 的狀態
  2. Handler 邏輯簡單oidcCallbackHandler 不需要「讀 pending cookie → 建 new logged-in cookie → 清 pending cookie」三步操作改 RotateSessionID 一步搞定
  3. Store 操作減少:每個 cookie 對應一個 store record兩個 cookie 等於 store 寫入次數加倍
  4. 防護機制已到位上述三道防線middleware UserID 檢查 + RotateSessionID + 同一次清理)涵蓋原本兩個 cookie 設計要解的問題
  1. 大改動:要動 usersession.Manageroidc_auth.go 兩處核心 handler、middleware、所有相關測試
  2. 不解新風險pending vs logged-in 的混淆風險已由現有防線阻擋;拆兩個 cookie 只是把同一個保護換個位置實作
  3. Phase 1 Redis 化更複雜:兩個 cookie 對應兩個 store keyRedis 命名空間 / TTL 管理變複雜;合一設計直接共用一個 key
  4. TDD §4.5 是文件示意:原設計是教學性的「直覺易懂」呈現,不是經過威脅模型分析的最佳化方案

取捨 (Trade-offs)

優點

  • Cookie 數量少browser cookie jar 簡潔DevTools 偵錯清楚
  • 邏輯路徑短handler / middleware / store 都只處理一種 cookie + 一種 store record
  • Phase 1 換 Redis 影響面小:同一個 Redis key 即可,不需要兩套 namespace
  • 測試簡單:不需要在每個 test 中模擬「兩個 cookie 同時存在 / 其一缺失」的所有組合

缺點

  • 理論上 pending 與 logged-in 共用 store record,若 middleware UserID 檢查被誤刪會立刻變漏洞
    • 緩解middleware 該行加註解明確警告不可拿掉CI 可加 grep 檢查
  • Session struct 同時有 OIDC pending 欄位 + user info 欄位序列化時負擔略大Phase 1 Redis 化時感受得到)
    • 緩解Phase 1 評估是否分兩個 struct但仍共用同一個 Redis key + 不同的序列化 schema
  • 與 oidc-tdd.md §4.5 文件示意不符
    • 緩解TDD §4.5 已加註「實際採合一設計,詳見 ADR-012」後續更新

影響範圍

區塊 影響
internal/usersession/usersession.go Session struct 同時包含 OIDC pending + user info 欄位(已實作)
internal/usersession/manager.go 新增 RotateSessionIDFix-A1做為 fixation 防護
internal/api/oidc_auth.go callback 流程state 比對 → ExchangeCode → VerifyIDToken → RotateSessionID → set user info → UpdateSession
internal/api/middleware.go AuthMiddleware 強制檢查 UserID == "" → 401不可拿掉
Phase 1 Redis 化 pending 與 logged-in 共用同一個 Redis key + 同一個 schema不影響架構

關聯

  • 推翻:無(補充 ADR-010 的實作細節決策)
  • 相關:ADR-010ADR-011
  • 配套修復Fix-A1RotateSessionID、Fix-A2本 ADR + middleware 註解)
  • 相關 review.autoflow/05-implementation/review/oidc-G5-OB1-OB6-review.md Major-3