emma-hermes/skills/devops/gateway-identity-binding-li.../references/line-telegram-verification-...

11 KiB
Raw Blame History

LINE / Telegram 驗證與授權生命週期說明

給未來修改 LINE / Telegram 驗證、配對、home-channel、email handoff、刪綁定與 admin 後台流程的人看。

這份文件是程式碼註解的唯一長文件入口。若你在 gateway/user_verification.pygateway/authz_mixin.pyplugins/platforms/telegram/adapter.pyplugins/platforms/line/adapter.pyhermes_cli/web_server.py 看到引用,請回到這裡讀完整背景。

為什麼需要這份文件

LINE / Telegram 驗證與授權不是單一布林值,而是多層狀態的組合。過去重複出現的 regressions 幾乎都來自下面幾種錯誤假設:

  1. 以為刪掉綁定 row 就等於失效
    • 實際上 pairing approval 仍可能放行。
  2. 以為 adapter allowlist 與 runtime 驗證是同一層
    • 實際上 adapter intake gate 與 gateway authz 是兩層,不同平台策略也不同。
  3. 以為 verified email handoff 只要看使用者輸入的 email
    • 實際上必須再比對 live 綁定,否則會把結果寄到過期或錯誤信箱。
  4. 以為 LINE / Telegram 可以共用完全相同的 intake 邏輯
    • 實際上 Telegram 與 LINE 的 prefilter 行為、reply token、quota fallback、pairing/onboarding 路徑都不同。
  5. 以為只要測 DB 或只要測 UI 就夠
    • 實際上需要同時驗證 DB、pairing store、adapter intake、gateway authz、admin route 與 user-visible 行為。

需求目標

系統對 LINE / Telegram 應滿足以下不變條件:

  1. 未驗證的新 DM 使用者可以進入 onboarding / pairing / company-email 驗證流程。
  2. 已驗證且仍綁定的使用者,可以被辨識為 verified source並安全使用 email handoff。
  3. 解除綁定時,所有仍可放行的授權面都要一起撤銷,而不是只刪一張表。
  4. group / room / forum 等非 DM 面向,不能因為修 DM onboarding 而意外放開。
  5. 系統自動 email handoff 必須重新比對 live 綁定,不可依賴舊 session、壓縮後上下文或快取中的 email。
  6. 任何改動都要避免 fail-open沒有明確 allowlist / pairing / verified binding 時,不應默默放行外部來源。

關鍵資料面(授權不是只有一處)

1. Runtime DBgateway_users.db

主要表:

  • principals
    • canonical principal例如 dk96@bremen.com.tw
  • external_identities
    • 平台帳號與 principal 的 verified 綁定關係
  • identity_enforcement
    • 某平台帳號目前驗證狀態(new, pending_verification, verified, admin_blocked, ...
  • verification_requests
    • 驗證 email token 與 resend 節流相關資料

2. Pairing store~/.hermes/pairing/{platform}-approved.json

這是獨立授權面

  • pairing approved 使用者在 gateway authz 層會被視為已授權
  • 即使 external_identities row 被刪掉,只要 approved 還在,仍可能可以對話

3. Adapter intake policy

平台 adapter 在訊息剛進來時,可能先做第一層 gate

  • Telegram_is_user_authorized_from_message()
  • LINE_allowed_for_source(...) 搭配 DM / group / room 分流

4. Gateway authz

在 adapter 之後gateway 還會再做 _is_user_authorized(...)

這一層會綜合:

  • pairing approved
  • env allowlists
  • adapter policy
  • allow-all flags
  • 群組/論壇例外規則

真正的生命週期

A. 新使用者從 Telegram / LINE DM 進來

  1. adapter 收到 inbound message
  2. adapter intake policy 決定:
    • 是否立刻丟棄
    • 是否把 DM 交給 gateway authz / verification
  3. gateway authz 檢查:
    • pairing approved
    • allowlist
    • verified binding
  4. 若尚未驗證,但平台策略允許 onboarding
    • 進入 company email 驗證流程
    • 建立 verification_requests
  5. 使用者點 email 驗證連結後:
    • 寫入 external_identities
    • 更新 identity_enforcement.state = verified

B. 已驗證使用者正常對話

  1. is_verified_source(source) 應同時成立:
    • identity_enforcement.state == verified
    • external_identities 仍存在對應 row
  2. 若系統要把結果改寄 email
    • 必須使用 bound_email_matches_source(...)
    • 嚴格比對 live 綁定中的 verified email

C. Admin 後台 force bind

  1. 建立 / 更新 principals
  2. 建立 / 更新 external_identities
  3. identity_enforcement 設為 verified
  4. 之後 email handoff / verified-source lookup 才能正確工作

D. Admin 後台 unbind最容易出 bug

解除綁定不是只做一件事,正確流程應是:

  1. 刪除 external_identities(platform, external_user_id)
  2. identity_enforcement 重設回 new
  3. 同步撤銷 pairing approval
  4. 寫 audit log最好記下 pairing_revoked=true/false
  5. 必要時再檢查 home-channel / control-channel 是否仍引用該 chat

2026-07-28 修正:GatewayUserStore.unbind_identity() 已同步呼叫 PairingStore().revoke(platform, external_user_id)

這是為了修正「刪綁定後仍能聊天」的通案問題,因為單刪 DB row 不足以撤銷所有授權面。

平台差異Telegram vs LINE

Telegram

重點:

  • plugins/platforms/telegram/adapter.py::_is_user_authorized_from_message
  • 這是 intake prefilter,在 text batching 與 event construction 之前執行
  • 原則:
    • 有明確 allowlist 時,可早期拒絕
    • 未知 DM 且沒有 allowlist 時,不能過早 default-deny
    • 否則新使用者無法進入 pairing / 驗證流程

LINE

重點:

  • plugins/platforms/line/adapter.py 的 allowlist gate 與 verified-email handoff 路徑
  • 原則:
    • DM user source 要能進 gateway authz / verification 層
    • group / room source 仍可在 adapter 層直接擋掉
    • 若太早在 adapter 層拒絕 DM使用者會看到「完全沒反應」尤其是 restart 後最容易誤判成壞掉
  • LINE 還有平台特有問題:
    • reply token 60 秒上下壽命
    • push quota / 429月額度
    • 改寄 email 時仍需驗證 live 綁定 email

已知高風險 bug 類型

1. Unbind 只刪 DB沒 revoke pairing

症狀:

  • 後台顯示已解綁
  • runtime DB 查不到 binding
  • 但使用者仍可直接對話

根因:

  • pairing approved 是獨立授權面

修法:

  • GatewayUserStore.unbind_identity() 必須同步 revoke pairing

2. Telegram prefilter 太早 default-deny

症狀:

  • 新使用者 DM 根本進不了 onboarding
  • 看起來像 bot 靜音

根因:

  • 在沒有 allowlist 的情況下,把未知 DM 直接擋掉

修法:

  • 無 allowlist 時要讓未知 DM 進到正常 pairing / verification flow

3. LINE adapter 太早拒絕 DM

症狀:

  • restart 後或新使用者第一次來訊時LINE 看起來完全沒反應

根因:

  • adapter 在 DM 層就把 unauthorized source 擋掉,沒讓 gateway authz 判斷

修法:

  • user-type source 應 defer 到 gateway authzgroup/room 才維持 adapter-gated

4. Email handoff 用到舊綁定或錯綁 email

症狀:

  • LINE/Telegram 結果被寄到不該寄的信箱
  • 或因綁定已變更而 handoff 失敗

根因:

  • 沒用 bound_email_matches_source(...) 重新比對 live verified email

修法:

  • 所有 system-initiated email handoff在送出前都要做 live 比對

5. 只測 admin route不測 user-visible 行為

症狀:

  • API 200 OK但使用者實際還是能聊 / 或根本進不了驗證

根因:

  • 只驗 route contract沒驗 pairing store / authz / adapter gating

修法:

  • 同時驗 DB、pairing store、adapter、gateway authz 與對話行為

修改程式時必看檔案

核心驗證 / 綁定 / 解綁

  • gateway/user_verification.py
    • is_verified_source
    • get_verified_email_for_source
    • bound_email_matches_source
    • process_inbound_message
    • force_bind_identity
    • unbind_identity

Gateway authz

  • gateway/authz_mixin.py
    • pairing approved 與 allowlist / adapter policy 的聯集規則

Telegram intake prefilter

  • plugins/platforms/telegram/adapter.py
    • _is_user_authorized_from_message

LINE intake / fallback / verified-email handoff

  • plugins/platforms/line/adapter.py
    • allowlist gateDM defer vs group/room reject
    • _verified_email_for_chat
    • _verified_email_target_if_bound
    • email fallback / quota fallback 路徑

Admin route

  • hermes_cli/web_server.py
    • /api/admin/verification/unbind
    • /api/admin/verification/force-bind
    • /api/pairing/revoke

修改守則(請當成 checklist

  1. 不要只改一層。
    • 驗證問題通常同時跨 adapter、authz、runtime DB、pairing store。
  2. 解除綁定一定要想 pairing。
    • 問自己:external_identities 沒了之後,還有哪個面會放行?
  3. 處理 email handoff 一定要比對 live verified email。
    • 不可依賴 session 中舊 email。
  4. Telegram / LINE 新使用者 DM 要能進 onboarding。
    • 不可因為強化安全而把 onboarding 一起封死。
  5. group / room / forum 的規則要和 DM 分開想。
    • 很多 regressions 來自把 DM 修法錯套到群組面。
  6. 若改到授權規則,請同步補回歸測試。
    • 尤其是 unbind、pairing、verified email handoff。
  7. 如果 user-visible 行為依賴 restart 才生效,要在變更說明寫清楚。

建議最少測試集

詳細測試地圖、對應檔案職責與建議執行順序,請直接看:

  • references/line-telegram-test-map.md
  • references/line-telegram-must-run-commands.md

已存在且這次相關的測試

  • tests/hermes_cli/test_web_verification_admin.py
  • tests/gateway/test_pairing.py
  • tests/gateway/test_pairing_allowlist_bypass.py
  • tests/gateway/test_line_plugin.py
  • tests/gateway/test_telegram_auth_check.py
  • tests/gateway/test_verified_email_handoff_guard_helpers.py
  • tests/gateway/test_line_telegram_verification_smoke.py

變更 unbind / pairing 時至少要驗

  1. force-bind 成功
  2. unbind 後:
    • external_identities row 消失
    • identity_enforcement.state == new
    • pairing approved 也消失
  3. audit log 有記下是否撤銷 pairing

變更 LINE / Telegram intake 時至少要驗

  1. 新 DM 使用者是否還能進 onboarding
  2. group / room / forum 是否沒有被意外放開
  3. 沒有 allowlist 時,是否仍維持預期安全邊界

變更 email handoff 時至少要驗

  1. live verified email match 才能送
  2. 綁定改掉後舊 email 不應再被使用
  3. LINE 429 / quota fallback 時仍不能繞過 live 綁定檢查

給未來維護者的一句話

如果你正在改 LINE / Telegram 驗證流程,請把它當成 多層授權狀態機,不要當成「單一路由 + 一張表」的 CRUD 問題。

這套東西最怕的是:

  • 看起來修好了
  • 後台也 200 OK
  • 但另一個授權面還在放行

那種 bug 會反覆回來嚇人,而且每次都像新 bug其實只是同一顆地雷換角度爆炸。