-
Notifications
You must be signed in to change notification settings - Fork 6
docs(bot-security): update Gateway Pairing section — document list_pending schema, CLI subcommands, and REST API (follow-up to PraisonAI#1520) #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -214,9 +214,6 @@ WhatsApp has the **strongest security defaults** and serves as the reference imp | |
|
|
||
| ## Gateway Pairing | ||
|
|
||
| <Warning> | ||
| **Note:** The pairing system described below represents planned functionality. Current SDK implementation may differ. Verify against actual SDK documentation. | ||
| </Warning> | ||
|
|
||
| For production deployments, use **gateway pairing** to authorize channels dynamically: | ||
|
|
||
|
|
@@ -233,8 +230,7 @@ Without `PRAISONAI_GATEWAY_SECRET`, pairing codes will **not persist across rest | |
| ### 2. Generate Pairing Code | ||
|
|
||
| ```python | ||
| # Note: This API is conceptual - verify implementation | ||
| from praisonaiagents.gateway.pairing import PairingStore | ||
| from praisonai.gateway.pairing import PairingStore | ||
|
|
||
| store = PairingStore() | ||
| code = store.generate_code(channel_type="telegram") | ||
|
|
@@ -254,8 +250,7 @@ The bot will verify the HMAC signature and authorize the channel. | |
| ### 4. Check Status | ||
|
|
||
| ```python | ||
| # Check if channel is paired | ||
| # Note: Verify this API exists in current SDK | ||
| # Check if channel is paired | ||
| paired = store.is_paired("@username", "telegram") | ||
| print(f"Channel paired: {paired}") | ||
|
|
||
|
|
@@ -264,6 +259,128 @@ for channel in store.list_paired(): | |
| print(f"{channel.channel_type}: {channel.channel_id}") | ||
| ``` | ||
|
|
||
| ### 5. List Pending Requests | ||
|
|
||
| List all pending pairing codes waiting for approval: | ||
|
|
||
| ```python | ||
| from praisonai.gateway.pairing import PairingStore | ||
|
|
||
| store = PairingStore() | ||
|
|
||
| # All pending codes across every channel | ||
| for req in store.list_pending(): | ||
| print(req["code"], req["channel_type"], req["channel_id"], req["age_seconds"]) | ||
|
|
||
| # Filter by channel | ||
| telegram_only = store.list_pending(channel_type="telegram") | ||
| ``` | ||
|
|
||
| **Response Schema:** | ||
|
|
||
| | Key | Type | Source | Notes | | ||
| |-----|------|--------|-------| | ||
| | `code` | `str` | canonical | 8-char pairing code | | ||
| | `channel_type` | `str` | canonical | e.g. `"telegram"`, `"discord"`, `"slack"`, `"whatsapp"` | | ||
| | `channel_id` | `str \| None` | canonical | Bound channel id if code was generated with one | | ||
| | `created_at` | `float` | canonical | Unix timestamp (seconds) when code was generated | | ||
| | `channel` | `str` | UI alias | Same value as `channel_type`, kept for UI banner compatibility | | ||
| | `user_id` | `str` | UI alias | Currently equals `code` (see note in `approve()` docstring) | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| | `user_name` | `str` | UI alias | Formatted as `"User {code}"` | | ||
| | `age_seconds` | `int` | UI alias | `int(now - created_at)` | | ||
|
|
||
| <Note> | ||
| Canonical keys (`code`, `channel_type`, `channel_id`, `created_at`) are the stable contract. The `channel`, `user_id`, `user_name`, and `age_seconds` aliases are provided for UI consumers and should not be relied on for scripting — use the canonical keys. | ||
| </Note> | ||
|
|
||
| ### 6. CLI Commands | ||
|
|
||
| Use the `praisonai pairing` commands to manage pairings from the command line: | ||
|
|
||
| ```bash | ||
| # List all paired channels | ||
| praisonai pairing list | ||
|
|
||
| # Approve a pairing code (this is the exact command shown to users) | ||
| praisonai pairing approve telegram abc12345 | ||
|
|
||
| # Revoke a paired channel | ||
| praisonai pairing revoke telegram @username | ||
|
|
||
| # Clear all paired channels | ||
| praisonai pairing clear | ||
| ``` | ||
|
|
||
| **Available Commands:** | ||
|
|
||
| | Command | Purpose | Required Args | | ||
| |---------|---------|---------------| | ||
| | `praisonai pairing list` | List all paired channels | — | | ||
| | `praisonai pairing approve PLATFORM CODE [CHANNEL_ID]` | Approve an 8-char pairing code | `platform`, `code` | | ||
| | `praisonai pairing revoke PLATFORM CHANNEL_ID` | Revoke a paired channel | `platform`, `channel_id` | | ||
| | `praisonai pairing clear` | Clear all paired channels | — | | ||
|
|
||
| **Platform values:** `telegram`, `discord`, `slack`, `whatsapp` | ||
|
|
||
| **Pairing Flow:** | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant User | ||
| participant Bot | ||
| participant PairingStore | ||
| participant CLI | ||
|
|
||
| User->>Bot: unknown message (triggers pairing) | ||
| Bot->>PairingStore: generate_code(channel_type, channel_id) | ||
| PairingStore-->>Bot: abc12345 | ||
| Bot->>User: Ask owner to run: praisonai pairing approve telegram abc12345 | ||
| CLI->>PairingStore: approve(telegram, abc12345) | ||
|
Comment on lines
+335
to
+338
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two discrepancies in this sequence diagram compared to the implementation in
|
||
| PairingStore->>PairingStore: verify_and_pair() | ||
| PairingStore-->>CLI: success | ||
| User->>Bot: now authorised | ||
| Bot-->>User: response | ||
|
|
||
| classDef user fill:#8B0000,stroke:#7C90A0,color:#fff | ||
| classDef bot fill:#189AB4,stroke:#7C90A0,color:#fff | ||
| classDef success fill:#10B981,stroke:#7C90A0,color:#fff | ||
|
|
||
| class User user | ||
| class Bot,PairingStore,CLI bot | ||
| ``` | ||
|
|
||
| ### 7. REST API | ||
|
|
||
| The gateway exposes REST endpoints for pairing management: | ||
|
|
||
| | Method | Path | Body / Query | Response | Auth Required | | ||
| |--------|------|--------------|----------|---------------| | ||
| | `GET` | `/api/pairing/pending` | — | `list_pending()` schema | ✅ | | ||
| | `POST` | `/api/pairing/approve` | `{ "channel": str, "code": str }` | `{ "approved": true, ... }` | ✅ | | ||
| | `POST` | `/api/pairing/revoke` | `{ "channel": str, "user_id": str }` | `{ "revoked": true, ... }` | ✅ | | ||
|
Comment on lines
+358
to
+360
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| **Example Usage:** | ||
|
|
||
| ```bash | ||
| # List pending requests | ||
| curl -H "Authorization: Bearer $TOKEN" \ | ||
| http://localhost:8000/api/pairing/pending | ||
|
|
||
| # Approve a code | ||
| curl -X POST -H "Authorization: Bearer $TOKEN" \ | ||
| -d '{"channel":"telegram","code":"abc12345"}' \ | ||
| http://localhost:8000/api/pairing/approve | ||
|
|
||
| # Revoke a channel | ||
| curl -X POST -H "Authorization: Bearer $TOKEN" \ | ||
| -d '{"channel":"telegram","user_id":"@username"}' \ | ||
| http://localhost:8000/api/pairing/revoke | ||
| ``` | ||
|
|
||
| <Note> | ||
| All endpoints are **authenticated** and **rate-limited**. Rate limits are applied per client IP with separate buckets for `pairing_pending`, `pairing_approve`, and `pairing_revoke` operations. | ||
| </Note> | ||
|
|
||
| ## Doctor Security Check | ||
|
|
||
| <Warning> | ||
|
|
@@ -472,6 +589,11 @@ def is_verified_user(user_id: str) -> bool: | |
| 1. Set `PRAISONAI_GATEWAY_SECRET` env var | ||
| 2. Codes without persistent secret are temporary | ||
|
|
||
| **Problem:** `praisonai pairing approve` reports "Invalid or expired code" even though a code was just generated from the UI | ||
| **Solution:** | ||
| 1. Upgrade to the latest `praisonai` version (fix included in 2026-04-22 release) | ||
| 2. Older builds had a duplicate internal method that stripped the canonical `code` key when the UI pairing banner was loaded | ||
|
|
||
| ### Allowlist Issues | ||
|
|
||
| **Problem:** Bot not responding to allowed users | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The schema indicates that
channel_idis a canonical key stored during code generation. However, the current implementation ofgenerate_codeinpraisonai/gateway/pairing.py(line 113) does not accept achannel_idargument and does not store it in the_pendingdictionary. This will causereq["channel_id"]to be missing or incorrect in the response.