11 KiB
LINE / Telegram 驗證與授權生命週期說明
給未來修改 LINE / Telegram 驗證、配對、home-channel、email handoff、刪綁定與 admin 後台流程的人看。
這份文件是程式碼註解的唯一長文件入口。若你在
gateway/user_verification.py、gateway/authz_mixin.py、plugins/platforms/telegram/adapter.py、plugins/platforms/line/adapter.py、hermes_cli/web_server.py看到引用,請回到這裡讀完整背景。
為什麼需要這份文件
LINE / Telegram 驗證與授權不是單一布林值,而是多層狀態的組合。過去重複出現的 regressions 幾乎都來自下面幾種錯誤假設:
- 以為刪掉綁定 row 就等於失效
- 實際上 pairing approval 仍可能放行。
- 以為 adapter allowlist 與 runtime 驗證是同一層
- 實際上 adapter intake gate 與 gateway authz 是兩層,不同平台策略也不同。
- 以為 verified email handoff 只要看使用者輸入的 email
- 實際上必須再比對 live 綁定,否則會把結果寄到過期或錯誤信箱。
- 以為 LINE / Telegram 可以共用完全相同的 intake 邏輯
- 實際上 Telegram 與 LINE 的 prefilter 行為、reply token、quota fallback、pairing/onboarding 路徑都不同。
- 以為只要測 DB 或只要測 UI 就夠
- 實際上需要同時驗證 DB、pairing store、adapter intake、gateway authz、admin route 與 user-visible 行為。
需求目標
系統對 LINE / Telegram 應滿足以下不變條件:
- 未驗證的新 DM 使用者可以進入 onboarding / pairing / company-email 驗證流程。
- 已驗證且仍綁定的使用者,可以被辨識為 verified source,並安全使用 email handoff。
- 解除綁定時,所有仍可放行的授權面都要一起撤銷,而不是只刪一張表。
- group / room / forum 等非 DM 面向,不能因為修 DM onboarding 而意外放開。
- 系統自動 email handoff 必須重新比對 live 綁定,不可依賴舊 session、壓縮後上下文或快取中的 email。
- 任何改動都要避免 fail-open:沒有明確 allowlist / pairing / verified binding 時,不應默默放行外部來源。
關鍵資料面(授權不是只有一處)
1. Runtime DB:gateway_users.db
主要表:
principals- canonical principal(例如
dk96@bremen.com.tw)
- canonical principal(例如
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_identitiesrow 被刪掉,只要 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 進來
- adapter 收到 inbound message
- adapter intake policy 決定:
- 是否立刻丟棄
- 是否把 DM 交給 gateway authz / verification
- gateway authz 檢查:
- pairing approved?
- allowlist?
- verified binding?
- 若尚未驗證,但平台策略允許 onboarding:
- 進入 company email 驗證流程
- 建立
verification_requests
- 使用者點 email 驗證連結後:
- 寫入
external_identities - 更新
identity_enforcement.state = verified
- 寫入
B. 已驗證使用者正常對話
is_verified_source(source)應同時成立:identity_enforcement.state == verifiedexternal_identities仍存在對應 row
- 若系統要把結果改寄 email:
- 必須使用
bound_email_matches_source(...) - 嚴格比對 live 綁定中的 verified email
- 必須使用
C. Admin 後台 force bind
- 建立 / 更新
principals - 建立 / 更新
external_identities - 將
identity_enforcement設為verified - 之後 email handoff / verified-source lookup 才能正確工作
D. Admin 後台 unbind(最容易出 bug)
解除綁定不是只做一件事,正確流程應是:
- 刪除
external_identities(platform, external_user_id) - 把
identity_enforcement重設回new - 同步撤銷 pairing approval
- 寫 audit log,最好記下
pairing_revoked=true/false - 必要時再檢查 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 authz;group/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.pyis_verified_sourceget_verified_email_for_sourcebound_email_matches_sourceprocess_inbound_messageforce_bind_identityunbind_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 gate(DM 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)
- 不要只改一層。
- 驗證問題通常同時跨 adapter、authz、runtime DB、pairing store。
- 解除綁定一定要想 pairing。
- 問自己:
external_identities沒了之後,還有哪個面會放行?
- 問自己:
- 處理 email handoff 一定要比對 live verified email。
- 不可依賴 session 中舊 email。
- Telegram / LINE 新使用者 DM 要能進 onboarding。
- 不可因為強化安全而把 onboarding 一起封死。
- group / room / forum 的規則要和 DM 分開想。
- 很多 regressions 來自把 DM 修法錯套到群組面。
- 若改到授權規則,請同步補回歸測試。
- 尤其是 unbind、pairing、verified email handoff。
- 如果 user-visible 行為依賴 restart 才生效,要在變更說明寫清楚。
建議最少測試集
詳細測試地圖、對應檔案職責與建議執行順序,請直接看:
references/line-telegram-test-map.mdreferences/line-telegram-must-run-commands.md
已存在且這次相關的測試
tests/hermes_cli/test_web_verification_admin.pytests/gateway/test_pairing.pytests/gateway/test_pairing_allowlist_bypass.pytests/gateway/test_line_plugin.pytests/gateway/test_telegram_auth_check.pytests/gateway/test_verified_email_handoff_guard_helpers.pytests/gateway/test_line_telegram_verification_smoke.py
變更 unbind / pairing 時至少要驗
- force-bind 成功
- unbind 後:
external_identitiesrow 消失identity_enforcement.state == new- pairing approved 也消失
- audit log 有記下是否撤銷 pairing
變更 LINE / Telegram intake 時至少要驗
- 新 DM 使用者是否還能進 onboarding
- group / room / forum 是否沒有被意外放開
- 沒有 allowlist 時,是否仍維持預期安全邊界
變更 email handoff 時至少要驗
- live verified email match 才能送
- 綁定改掉後舊 email 不應再被使用
- LINE 429 / quota fallback 時仍不能繞過 live 綁定檢查
給未來維護者的一句話
如果你正在改 LINE / Telegram 驗證流程,請把它當成 多層授權狀態機,不要當成「單一路由 + 一張表」的 CRUD 問題。
這套東西最怕的是:
- 看起來修好了
- 後台也 200 OK
- 但另一個授權面還在放行
那種 bug 會反覆回來嚇人,而且每次都像新 bug,其實只是同一顆地雷換角度爆炸。