mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-17 13:05:58 +00:00
0966131b31
* fix(channels): require bound identity for user-owned IM messages * make format * docs: document bound identity channel config * refactor: reuse channel connection config * refactor _requires_bound_identity() * refactor from_app_config() * make format * fix: reject unbound channel chats before semaphore * security enhancement * make format * fix: enforce bound-identity admission at command entry point The bound-identity gate only ran for non-command messages in _handle_message() and as a fallback inside _handle_chat(). Commands had no equivalent boundary, so an unbound platform user could send /new and reach _create_thread() directly, creating an unowned Gateway thread and empty checkpoint. Info commands (/status, /models, /memory) likewise leaked Gateway state to unbound users. Add the same _requires_bound_identity() check at the top of _handle_command(), rejecting via _reject_unbound_channel_message() before any thread creation or Gateway query. The gate is a no-op in legacy open-bot mode (require_bound_identity=False) and auth-disabled mode. Provider-level binding flows (/connect, /start) are consumed by the provider adapter before reaching the manager, so they are unaffected. Tests: - unbound auth-enabled /new is rejected before threads.create - bound auth-enabled /new still creates the thread Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(channels): carry workspace fallback decision on inbound messages * fix(channels): recheck bound identity by normalized workspace * fix(channels): avoid duplicate bound identity checks * fix(channels): preserve verified routing for bound identity rejects * fix(channels): clarify bound identity upgrade failures --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
132 lines
5.0 KiB
Markdown
132 lines
5.0 KiB
Markdown
# IM Channel Connections
|
|
|
|
DeerFlow supports user-owned IM channel bindings for Telegram, Slack, Discord, Feishu/Lark, DingTalk, WeChat, and WeCom. The feature reuses the existing `channels.*` runtime configuration, so it works in local and private deployments with the same outbound transports already supported by DeerFlow.
|
|
|
|
No public IP, OAuth callback URL, or provider webhook is required in this implementation.
|
|
|
|
## Configuration
|
|
|
|
Configure the actual IM bots under the existing `channels` block:
|
|
|
|
```yaml
|
|
channels:
|
|
telegram:
|
|
enabled: true
|
|
bot_token: $TELEGRAM_BOT_TOKEN
|
|
|
|
slack:
|
|
enabled: true
|
|
bot_token: $SLACK_BOT_TOKEN
|
|
app_token: $SLACK_APP_TOKEN
|
|
|
|
discord:
|
|
enabled: true
|
|
bot_token: $DISCORD_BOT_TOKEN
|
|
|
|
feishu:
|
|
enabled: true
|
|
app_id: $FEISHU_APP_ID
|
|
app_secret: $FEISHU_APP_SECRET
|
|
|
|
dingtalk:
|
|
enabled: true
|
|
client_id: $DINGTALK_CLIENT_ID
|
|
client_secret: $DINGTALK_CLIENT_SECRET
|
|
|
|
wechat:
|
|
enabled: true
|
|
bot_token: $WECHAT_BOT_TOKEN
|
|
|
|
wecom:
|
|
enabled: true
|
|
bot_id: $WECOM_BOT_ID
|
|
bot_secret: $WECOM_BOT_SECRET
|
|
```
|
|
|
|
Then enable user bindings in `channel_connections`:
|
|
|
|
```yaml
|
|
channel_connections:
|
|
enabled: true
|
|
# Auth-enabled deployments require ordinary IM messages to come from a
|
|
# connected DeerFlow user by default. Set this to false only for legacy
|
|
# operator-owned/open-bot deployments that intentionally route unbound
|
|
# platform users to platform-ID user buckets.
|
|
require_bound_identity: true
|
|
|
|
telegram:
|
|
enabled: true
|
|
bot_username: $TELEGRAM_BOT_USERNAME
|
|
|
|
slack:
|
|
enabled: true
|
|
|
|
discord:
|
|
enabled: true
|
|
|
|
feishu:
|
|
enabled: true
|
|
|
|
dingtalk:
|
|
enabled: true
|
|
|
|
wechat:
|
|
enabled: true
|
|
|
|
wecom:
|
|
enabled: true
|
|
```
|
|
|
|
`channel_connections` does not duplicate provider secrets. It only controls the browser-facing connect UI and stores per-user binding records. Telegram needs `bot_username` only so the frontend can open a deep link.
|
|
|
|
When `channel_connections.enabled` and `require_bound_identity` are true, auth-enabled deployments reject ordinary unbound IM messages before creating a DeerFlow thread or run. Users must connect the channel from DeerFlow Settings first. Auth-disabled local mode still routes channel messages to the auth-disabled default user, and legacy open-bot behavior can be restored explicitly with `require_bound_identity: false`.
|
|
|
|
Upgrade note: existing auth-enabled deployments that already have `channel_connections.enabled: true` will start rejecting ordinary unbound IM messages after this field is introduced because `require_bound_identity` defaults to true. Legacy operator-owned/open-bot deployments that intentionally allow unbound platform users to create DeerFlow runs should set `require_bound_identity: false` before upgrading and restart the service.
|
|
|
|
## Connect Flow
|
|
|
|
Telegram:
|
|
|
|
- The frontend creates a short one-time code.
|
|
- The Connect button opens `https://t.me/<bot_username>?start=<code>`.
|
|
- The existing Telegram long-polling worker receives `/start <code>` and binds that Telegram chat/user to the current DeerFlow user.
|
|
|
|
Slack:
|
|
|
|
- The frontend creates a short one-time code.
|
|
- The UI shows `Send /connect <code> to the DeerFlow Slack bot.`
|
|
- The existing Slack Socket Mode worker receives the message and binds the Slack user/team to the current DeerFlow user.
|
|
|
|
Discord:
|
|
|
|
- The frontend creates a short one-time code.
|
|
- The UI shows `Send /connect <code> to the DeerFlow Discord bot.`
|
|
- The existing Discord Gateway worker receives the message and binds the Discord user/guild to the current DeerFlow user.
|
|
|
|
Feishu/Lark, DingTalk, WeChat, and WeCom:
|
|
|
|
- The frontend creates a short one-time code.
|
|
- The UI shows `Send /connect <code> to the DeerFlow <Provider> bot.`
|
|
- The already-running long-connection or polling worker receives the message and binds the platform user/workspace identity to the current DeerFlow user.
|
|
|
|
Codes use 128 bits of randomness, expire after 10 minutes, and are single-use.
|
|
|
|
## Runtime Model
|
|
|
|
Connection records live in SQL tables under `deerflow.persistence.channel_connections`:
|
|
|
|
- `channel_connections`: owner user, provider identity, workspace/guild/team, status, metadata.
|
|
- `channel_oauth_states`: one-time connect codes and Telegram deep-link state.
|
|
- `channel_conversations`: connection-scoped IM conversation to DeerFlow thread mapping.
|
|
- `channel_credentials`: reserved for future provider-token flows, not used by the local/private binding flow.
|
|
|
|
Incoming messages that resolve to a connection carry `connection_id`, `owner_user_id`, and `workspace_id`. `ChannelManager` uses `owner_user_id` as the DeerFlow run user id and preserves the raw platform user id as `channel_user_id`.
|
|
|
|
## Security Notes
|
|
|
|
- Browser APIs remain authenticated and CSRF-protected.
|
|
- Connect codes are 128-bit random, short-lived, and single-use.
|
|
- Provider bot tokens remain in `channels.*` and are never returned to the browser.
|
|
- Stored per-connection credentials are encrypted. If stored credential material cannot be decrypted, DeerFlow treats it as unavailable instead of using corrupt secrets.
|
|
- This implementation does not add public provider callback or webhook routes.
|