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

293 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 幾乎都來自下面幾種錯誤假設:
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 DB`gateway_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其實只是同一顆地雷換角度爆炸。