feat: Platform bot linking API for multi-platform CoPilot#12615
feat: Platform bot linking API for multi-platform CoPilot#12615
Conversation
Adds the account linking system that enables CoPilot to work across
multiple chat platforms (Discord, Telegram, Slack, Teams, etc.) via
the Vercel Chat SDK.
## What this adds
### Database (Prisma + migration)
- PlatformType enum (DISCORD, TELEGRAM, SLACK, TEAMS, WHATSAPP, GITHUB, LINEAR)
- PlatformLink model - maps platform user IDs to AutoGPT accounts
- Unique constraint on (platform, platformUserId)
- One AutoGPT user can link multiple platforms
- PlatformLinkToken model - one-time tokens for the linking flow
### API endpoints (/api/platform-linking)
Bot-facing (called by the bot service):
- POST /tokens - Create a link token for an unlinked platform user
- GET /tokens/{token}/status - Check if linking is complete
- POST /resolve - Resolve platform identity → AutoGPT user ID
User-facing (JWT auth required):
- POST /tokens/{token}/confirm - Complete the link (user logs in first)
- GET /links - List all linked platform identities
- DELETE /links/{link_id} - Unlink a platform identity
## Linking flow
1. User messages bot on Discord/Telegram/etc
2. Bot calls POST /resolve → not linked
3. Bot calls POST /tokens → gets link URL
4. Bot sends user the link
5. User clicks → logs in to AutoGPT → frontend calls POST /confirm
6. Bot detects link on next message (or polls /status)
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Switched from backend.data.db.get_prisma() to the standard PlatformLink.prisma() / PlatformLinkToken.prisma() pattern used throughout the codebase.
|
/review |
Multi-platform bot service that deploys CoPilot to Discord, Telegram, and Slack from a single codebase using Vercel's Chat SDK. ## What's included ### Core bot (src/bot.ts) - Chat SDK instance with dynamic adapter loading - onNewMention: resolves platform user → AutoGPT account - Unlinked users get a link prompt via the platform-linking API - Subscribed message handler with state management - MVP echo response (CoPilot API integration next) ### Platform API client (src/platform-api.ts) - Calls /api/platform-linking/resolve on every message - Creates link tokens for unlinked users - Checks link token status - Chat session creation and SSE streaming (prepared for CoPilot) ### Serverless routes (src/api/) - POST /api/webhooks/discord — Discord interactions endpoint - POST /api/webhooks/telegram — Telegram updates - POST /api/webhooks/slack — Slack events - GET /api/gateway/discord — Gateway cron for Discord messages ### Standalone mode (src/index.ts) - Long-running process for Docker/PM2 deployment - Auto-detects enabled adapters from env vars - Redis or in-memory state ## Stacked on - feat/platform-bot-linking (PR #12615)
There was a problem hiding this comment.
All 8 specialists have reported. Now I have everything I need. Let me compile the final verdict.
PR #12615 — feat: Platform bot linking API for multi-platform CoPilot
Author: Bentlybro | Requested by: ntindle | Files: routes.py (+381), routes_test.py (+25), rest_api.py (+6), migration.sql (+47), schema.prisma (+48), openapi.json (+212)
🎯 Verdict: REQUEST_CHANGES
What This PR Does
Adds a new backend subsystem for linking external chat platform identities (Discord, Telegram, Slack, etc.) to AutoGPT user accounts. This enables a multi-platform CoPilot bot to know which AutoGPT account a chat user belongs to. The flow: bot creates a one-time link token → sends user a URL → user clicks it, logs in, and confirms → the platform identity is now mapped to their account. The PR includes API routes, Prisma schema/migration, and OpenAPI spec updates. No frontend UI is included — that's planned as a follow-up.
Specialist Findings
🛡️ Security 🔴 — Three bot-facing endpoints (POST /tokens, GET /tokens/{token}/status, POST /resolve) ship with zero authentication. The code has a TODO acknowledging this (routes.py:103-108). Any internet caller can: (1) flood the token table via POST /tokens (no rate limiting, no cap per user), (2) enumerate linked users via POST /resolve by probing platform IDs — leaking internal user_id and platform_username, (3) poll any token's status and extract the linked user_id. Additionally, confirm_link_token (routes.py:210-263) performs 5 separate DB calls with no transaction — a race condition on double-click can trigger an unhandled unique constraint violation (500 error instead of clean 409).
- 🔴
routes.py:98-108— Unauthenticated bot endpoints: must add API key/service token auth before merge - 🔴
routes.py:210-263— Race condition in confirm flow: no transaction wrapping check-and-consume - 🟠
routes.py:181-195—/resolveleaksuser_idto unauthenticated callers - 🟠
routes.py:158-160—/tokens/{token}/statusleaksuser_idwithout auth - 🟠
routes.py:98-140— Token flooding: unlimited pending tokens per platform user, no rate limit
🏗️ Architecture api/features/<name>/ convention correctly. Router registration is consistent. However: (1) Enum duplication — PlatformType is defined in Prisma schema AND as a Python VALID_PLATFORMS set (routes.py:34-41); these will drift when someone adds a platform to Prisma without updating the Python set. Fix: import from prisma.enums. (2) No service layer — other features (otto, mcp) separate routes from data access; this puts Prisma calls directly in handlers. (3) No expired token cleanup mechanism despite having an expiresAt index. (4) Hardcoded link_url base URL (routes.py:139) breaks staging/self-hosted. (5) Redundant @@index([token]) when @unique already creates an index on PlatformLinkToken.token.
⚠️ routes.py:34-41/schema.prisma— Enum duplication will drift⚠️ routes.py:139— Hardcodedhttps://platform.agpt.cobase URL⚠️ schema.prisma:1349— Redundant index ontoken(unique already indexes)
⚡ Performance ✅ — All endpoints are O(1) indexed lookups, which is correct. The hot path (POST /resolve, called on every bot message) hits the composite unique index — efficient. Concerns: (1) confirm_link_token does 4 DB round trips without a transaction (also a correctness issue). (2) POST /tokens does a check-then-create (2 round trips) that could be a single upsert. (3) Redundant @@index([token]) doubles write cost on that column. (4) Token table grows unbounded — no TTL/cleanup.
⚠️ schema.prisma:1349— Remove redundant@@index([token])⚠️ No token cleanup job — table grows forever
🧪 Testing 🔴 — Only 25 lines of tests covering a single trivial helper (_validate_platform). Zero endpoint tests across all 6 API routes. The codebase has well-established patterns (TestClient + mock_jwt_user fixture + mocked Prisma) in sibling features (chat/routes_test.py, otto/routes_test.py, library/routes_test.py). Missing: happy-path linking flow, expired/used token handling, duplicate link prevention (409s), ownership checks on DELETE, auth enforcement on user-facing endpoints, race condition handling on concurrent confirm. Estimated coverage: ~5% of new code.
- 🔴
routes_test.py— Only tests_validate_platform, zero endpoint coverage - Missing: ~20+ test cases for the 6 endpoints following existing codebase patterns
📖 Quality LinkTokenStatusResponse.status is bare str — should be Literal["pending", "linked", "expired"] (routes.py:67). (2) delete_link returns dict instead of a response model (routes.py:310). (3) platform fields across all models are bare str — should use a shared Literal/enum type. (4) No max_length/min_length on platform_user_id (routes.py:54). (5) Logger uses f-strings instead of lazy %s formatting. (6) OpenAPI operation IDs are auto-generated and ugly (e.g., "getPlatform-linkingCheck if a link token has been consumed").
⚠️ routes.py:67—status: str→ useLiteral⚠️ routes.py:310—-> dict→ proper response model⚠️ routes.py:54— No input length validation onplatform_user_id
📦 Product link_url points to https://platform.agpt.co/link/{token} which returns 404 because no frontend /link/{token} page exists yet. Error messages are developer-speak, not user-friendly (e.g., exposing internal IDs in 409 responses). The channelId field is stored but never used — hints at an unimplemented callback mechanism. Token expiry of 30 minutes is reasonable but tight for mobile users. No notification after linking completes — the bot must poll.
⚠️ Frontend/link/{token}page missing — link is a dead end today⚠️ routes.py:120— Error messages expose internal platform user IDs⚠️ routes.py:63—channelIdstored but never used (dead field)
📬 Discussion ✅ — CI passing (25/26 checks, only cosmetic skips). No human reviews submitted yet (reviewDecision: REVIEW_REQUIRED). CodeRabbit skipped (detected draft). Author's PR description explicitly lists follow-up TODOs: API key auth, frontend page, bot service, token cleanup, rate limiting. No linked tracking issues.
🔎 QA ✅ — All new API endpoints are functional and accessible. Live testing confirmed:
POST /resolvereturns correct responses (200 for valid, 422 for invalid input, 400 for bad platform)POST /tokenscreates tokens with properlink_urlandexpires_atGET /tokens/{token}/statusreturnspending/404correctly- Auth-protected endpoints (
GET /links,POST /confirm,DELETE /links/{id}) properly return 401 when unauthenticated - Frontend is completely unaffected — all pages (landing, copilot, library, build, marketplace) load correctly
- No console errors related to this PR
Blockers (Must Fix)
-
routes.py:98-108— Bot-facing endpoints have NO authentication.POST /tokens,GET /tokens/{token}/status, andPOST /resolveare completely open. Anyone can create tokens, enumerate linked users, and extract internal user IDs. TheTODOcomment is not an acceptable substitute. Add API key auth before merge. (Flagged by: Security 🔴, Architecture, Product, Discussion) -
routes.py:210-263— Race condition inconfirm_link_token. Five separate DB calls with no transaction. Concurrent requests can create duplicate links; the unique constraint violation surfaces as an unhandled 500 error. Wrap in a Prisma transaction or use atomic conditional update. (Flagged by: Security 🔴, Performance, Architecture) -
routes_test.py— Virtually no test coverage. Only 3 tests for a trivial helper. Zero endpoint tests across 6 routes and 381 lines of new code. The codebase has clear testing patterns that were not followed. Add at minimum: happy-path linking flow, expired/used token handling, duplicate prevention, auth enforcement. (Flagged by: Testing 🔴)
Should Fix (Follow-up OK)
routes.py:34-41/schema.prisma— Enum duplication.VALID_PLATFORMSPython set will drift fromPlatformTypePrisma enum. Import fromprisma.enumsinstead. (Architecture, Quality)routes.py:139— Hardcoded base URL.https://platform.agpt.co/link/{token}should be configurable via env var. Breaks staging/dev/self-hosted. (Architecture, Product)schema.prisma:1349— Redundant@@index([token]). The@uniquealready creates an index. Remove to avoid doubled write cost. (Performance, Architecture)routes.py:67— Loose typing.status: strshould beLiteral["pending", "linked", "expired"]. Similarly,platformfields across models should use a shared type. (Quality)routes.py:310—delete_linkreturnsdict. Only endpoint without a proper response model. DefineDeleteLinkResponse. (Quality)- Token table cleanup —
PlatformLinkTokenrows accumulate forever. Add a periodic cleanup job. (Architecture, Performance) routes.py:54— No input length validation onplatform_user_id. A trivially long string would be persisted. AddField(max_length=255, min_length=1). (Quality)routes.py:98-140— Token flooding. No cap on pending tokens per(platform, platformUserId). Limit to 1 active token per identity. (Security, Performance)
Risk Assessment
Merge risk: HIGH | Rollback: EASY (new feature, no changes to existing tables/routes)
The core design is sound and the implementation is clean, but the three blockers — unauthenticated security-sensitive endpoints, a race condition, and near-zero test coverage — must be resolved before this merges. The feature is additive with easy rollback (drop the migration, remove the router), which reduces deployment risk.
REVIEW_COMPLETE
PR: #12615
Verdict: REQUEST_CHANGES
Blockers: 3
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
Fixes all blockers and should-fix items from the automated review:
## Blockers fixed
1. **Bot-facing endpoint auth** — Added X-Bot-API-Key header auth for
all bot-facing endpoints (POST /tokens, GET /tokens/{token}/status,
POST /resolve). Configurable via PLATFORM_BOT_API_KEY env var.
Dev mode allows keyless access.
2. **Race condition in confirm_link_token** — Replaced 5 separate DB
calls with atomic update_many (WHERE token=X AND usedAt=NULL).
If another request consumed the token first, returns 410 cleanly
instead of a 500 unique constraint violation.
3. **Test coverage** — Added tests for: Platform enum, bot API key
auth (valid/invalid/missing), request validation (empty IDs,
too-long IDs, invalid platforms), response model typing.
## Should-fix items addressed
- **Enum duplication** — Created Python Platform(str, Enum) that
mirrors the Prisma PlatformType. Used throughout request models
for validation instead of bare strings.
- **Hardcoded base URL** — Now configurable via PLATFORM_LINK_BASE_URL
env var. Defaults to https://platform.agpt.co/link.
- **Redundant @@index([token])** — Removed from schema and migration.
The @unique already creates an index.
- **Literal status type** — LinkTokenStatusResponse.status is now
Literal['pending', 'linked', 'expired'] not bare str.
- **delete_link returns dict** — Now returns DeleteLinkResponse model.
- **Input length validation** — Added min_length=1, max_length=255
to platform_user_id and platform_username fields.
- **Token flooding** — Existing pending tokens are invalidated before
creating a new one (only 1 active token per platform identity).
- **Resolve leaks user info** — Removed platform_username from
resolve response (only returns user_id + linked boolean).
- **Error messages** — Sanitized to not expose internal IDs.
- **Logger** — Switched from f-strings to lazy %s formatting.
Multi-platform bot service that deploys CoPilot to Discord, Telegram, and Slack from a single codebase using Vercel's Chat SDK. ## What's included ### Core bot (src/bot.ts) - Chat SDK instance with dynamic adapter loading - onNewMention: resolves platform user → AutoGPT account - Unlinked users get a link prompt via the platform-linking API - Subscribed message handler with state management - MVP echo response (CoPilot API integration next) ### Platform API client (src/platform-api.ts) - Calls /api/platform-linking/resolve on every message - Creates link tokens for unlinked users - Checks link token status - Chat session creation and SSE streaming (prepared for CoPilot) ### Serverless routes (src/api/) - POST /api/webhooks/discord — Discord interactions endpoint - POST /api/webhooks/telegram — Telegram updates - POST /api/webhooks/slack — Slack events - GET /api/gateway/discord — Gateway cron for Discord messages ### Standalone mode (src/index.ts) - Long-running process for Docker/PM2 deployment - Auto-detects enabled adapters from env vars - Redis or in-memory state ## Stacked on - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
|
/review |
There was a problem hiding this comment.
All 8 specialists have reported. Compiling the final verdict now.
PR #12615 — feat: Platform bot linking API for multi-platform CoPilot
Author: Bentlybro | Requested by: ntindle | Files: routes.py (+442), routes_test.py (+136), rest_api.py (+6), schema.prisma (+50), migration.sql (+44), openapi.json (+236)
🎯 Verdict: REQUEST_CHANGES
What This PR Does
Adds a backend API for linking external chat platform identities (Discord, Telegram, Slack, etc.) to AutoGPT user accounts. A bot creates a one-time link token, the user clicks it to log in and confirm, and the bot can then resolve the user's identity on subsequent messages. The Prisma schema, migration, and OpenAPI spec are all included. No frontend UI yet (backend-only first PR).
Specialist Findings
🛡️ Security
routes.py:74 — API key comparison uses Python != (not constant-time). Must use hmac.compare_digest() to prevent timing side-channel attacks on the bot API key.
routes.py:56-68 — verify_bot_api_key is dead code with a stub body (# Check header → return None). Never called by any endpoint, but if someone wires it up by mistake, it would pass all requests. Remove entirely.
routes.py:60-66, 71-73 — Default deployment (BOT_API_KEY="", ENV=development) has zero bot authentication. An attacker could create tokens, resolve identities, and poll status freely. Needs a loud startup warning or require explicit opt-in for dev bypass.
routes.py:275-306 — TOCTOU race between token consumption and link creation. Two concurrent requests with different tokens for the same platform identity could both pass the find_first check; the DB unique constraint catches it but produces an unhandled 500 instead of a clean 409. Wrap in try/except.
🏗️ Architecture ✅ — Clean module structure follows existing backend/api/features/ patterns. Route registration is standard. Dual auth (API key for bot, JWT for user) is well-separated.
routes.py:143-145 — Header extraction via Depends(lambda req: ...) is a hack that breaks FastAPI's dependency injection (see QA finding below). Should be a proper Header() dependency or typed function.
PlatformLinkToken rows accumulate forever. The @@index([expiresAt]) suggests cleanup was planned but never implemented.
Platform enum manually mirrors Prisma PlatformType — adding a platform to one without the other causes runtime failures with no compile-time safety.
⚡ Performance ✅ — Indexes are appropriate for the query patterns. Unique constraint on (platform, platformUserId) covers the hot-path /resolve query.
routes.py:234-255 — /resolve is called on every incoming bot message with no caching. A TTL cache (60-300s) would eliminate most DB hits since link status rarely changes.
PlatformLinkToken(platform, platformUserId) — both create_link_token and confirm_link_token query by this pair.
routes.py:341-363 — list_my_links returns all results unbounded. Add pagination as a guardrail.
routes.py:245, 160, 303 — Uses find_first where find_unique with the compound key would be more efficient and intentional.
🧪 Testing
create_link_token, confirm_link_token, get_link_token_status, resolve_platform_user, list_my_links, delete_link
TestClient — the broken Depends(lambda req: ...) pattern would have been caught immediately by any API-level test
delete_link
routes_test.py:48,83,100 — Class-level imports (from ... import X inside class body, accessed via self.X) are non-idiomatic; use module-level imports
📖 Quality
routes.py:56-70 — Dead verify_bot_api_key function alongside working _check_bot_api_key is confusing. Two auth functions with overlapping names but different approaches.
routes.py:143-145 — # noqa: ARG005 on lambda dependencies signals a code smell — the lambda hack requires lint suppression.
routes.py:49 — LINK_BASE_URL has no trailing-slash guard; f"{LINK_BASE_URL}/{token}" would produce double-slash if configured with trailing /.
"name": "req", "in": "query", "required": true parameters on bot endpoints — a client-facing bug from the lambda Depends pattern.
📦 Product ✅ — The linking flow follows industry-standard patterns (similar to Slack OAuth linking). Token expiry of 30 minutes is reasonable. One-to-many (user → platforms) and one-to-one (platform identity → user) enforcement is correct. Error messages are user-friendly.
channelId is stored in PlatformLinkToken but never used — no completion notification to the user on the platform side after linking. Acceptable as a follow-up.
/link/{token} — users clicking the link will 404. Must be documented as a required follow-up.
platformUsername has no update mechanism — goes stale if user changes their display name. Consider updating on each /resolve call.
📬 Discussion ✅ — PR is in draft state. One bot review (autogpt-pr-reviewer) requested changes with 3 blockers and 8 should-fix items. Bentlybro addressed most in follow-up commits (7bf10c6, 8a91bd8, 781224f). No human reviews yet. CI passing (26/27 checks, one pending/skipped). No merge conflicts.
verify_bot_api_key dead code.
🔎 QA ❌ — CRITICAL BUG: All 3 bot-facing endpoints are non-functional.
🔴 routes.py:143-145, 179-181, 213-215 — Depends(lambda req: req.headers.get("x-bot-api-key")) does not work. FastAPI interprets the untyped req parameter as a required query parameter, not the Request object. Result: 422 "Field required" without ?req=, and 500 'str' object has no attribute 'headers' with it. POST /tokens, GET /tokens/{token}/status, and POST /resolve are all broken.
✅ User-facing endpoints work correctly: POST /tokens/{token}/confirm (401 without JWT, 404 for invalid tokens), GET /links (returns list, 401 without auth), DELETE /links/{id} (404 for nonexistent, 401 without auth)
✅ Frontend unaffected — landing page, login, signup, copilot dashboard, build page all load normally
Blockers (Must Fix)
-
routes.py:143-145, 179-181, 213-215— All bot-facing endpoints are broken. TheDepends(lambda req: req.headers.get("x-bot-api-key"))pattern fails because FastAPI cannot inferreqis theRequest. Fix:from fastapi import Request async def get_bot_api_key(request: Request) -> str | None: return request.headers.get("x-bot-api-key")
Then use
x_bot_api_key: str | None = Depends(get_bot_api_key)on each endpoint. (Flagged by QA ❌, Architecture 🏗️, Quality 📖) -
routes.py:74— API key comparison must usehmac.compare_digest(request_api_key, BOT_API_KEY)instead of!=to prevent timing side-channel attacks. (Flagged by Security 🛡️) -
routes.py:56-68— Remove the deadverify_bot_api_keyfunction entirely. It has a broken stub body and is never called, but could be mistakenly wired up as an auth dependency that passes everything. (Flagged by Security 🛡️, Architecture 🏗️, Quality 📖, Product 📦) -
routes.py:275-306— Wrap thePlatformLink.prisma().create()in a try/except forUniqueConstraintViolationto handle the TOCTOU race gracefully (return 409 instead of 500). (Flagged by Security 🛡️, Architecture 🏗️, Performance ⚡)
Should Fix (Follow-up OK)
routes.py:234-255— Add a TTL cache onresolve_platform_user— this is called on every bot message and hits the DB each time.routes_test.py— Add integration tests for route handlers usingTestClient. The lambda Depends bug would have been caught by any API-level test. At minimum: happy path for create/confirm/resolve/list/delete.migration.sql— Add composite indexPlatformLinkToken(platform, platformUserId)for the queries increate_link_tokenandconfirm_link_token.routes.py:341-363— Add pagination tolist_my_links(take/skip or cursor-based).routes.py:60-66, 71-73— Add startup warning whenBOT_API_KEYis empty, or require explicitDISABLE_BOT_AUTH=truefor dev mode.- Add a periodic cleanup job for expired
PlatformLinkTokenrows. - Document the frontend
/link/{token}route as a required follow-up in the PR description. openapi.json— The spurious"req"query parameter on bot endpoints will confuse API clients and generated SDKs. Resolves automatically when blocker #1 is fixed.
Risk Assessment
Merge risk: HIGH — Core feature endpoints (bot-facing) are non-functional due to the FastAPI dependency injection bug. If merged as-is, no bot could use this API.
Rollback: EASY — New feature with no changes to existing code paths. Drop migration, remove router, done.
REVIEW_COMPLETE
PR: #12615
Verdict: REQUEST_CHANGES
Blockers: 4
Adds two bot-facing endpoints that let the bot service call CoPilot on behalf of linked users without needing their JWT: - POST /api/platform-linking/chat/session — create a CoPilot session - POST /api/platform-linking/chat/stream — send message + stream SSE response Both authenticated via X-Bot-API-Key header. The bot passes user_id from the platform-linking resolve step. Backend reuses the existing CoPilot pipeline (enqueue_copilot_turn → Redis stream → SSE). This is the last missing piece — the bot can now: 1. Resolve platform user → AutoGPT account (existing) 2. Create a chat session (new) 3. Stream CoPilot responses back to the user (new)
Multi-platform bot service that deploys CoPilot to Discord, Telegram, and Slack from a single codebase using Vercel's Chat SDK. ## What's included ### Core bot (src/bot.ts) - Chat SDK instance with dynamic adapter loading - onNewMention: resolves platform user → AutoGPT account - Unlinked users get a link prompt via the platform-linking API - Subscribed message handler with state management - MVP echo response (CoPilot API integration next) ### Platform API client (src/platform-api.ts) - Calls /api/platform-linking/resolve on every message - Creates link tokens for unlinked users - Checks link token status - Chat session creation and SSE streaming (prepared for CoPilot) ### Serverless routes (src/api/) - POST /api/webhooks/discord — Discord interactions endpoint - POST /api/webhooks/telegram — Telegram updates - POST /api/webhooks/slack — Slack events - GET /api/gateway/discord — Gateway cron for Discord messages ### Standalone mode (src/index.ts) - Long-running process for Docker/PM2 deployment - Auto-detects enabled adapters from env vars - Redis or in-memory state ## Stacked on - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
Multi-platform bot service that deploys CoPilot to Discord, Telegram, and Slack from a single codebase using Vercel's Chat SDK. ## What's included ### Core bot (src/bot.ts) - Chat SDK instance with dynamic adapter loading - onNewMention: resolves platform user → AutoGPT account - Unlinked users get a link prompt via the platform-linking API - Subscribed message handler with state management - MVP echo response (CoPilot API integration next) ### Platform API client (src/platform-api.ts) - Calls /api/platform-linking/resolve on every message - Creates link tokens for unlinked users - Checks link token status - Chat session creation and SSE streaming (prepared for CoPilot) ### Serverless routes (src/api/) - POST /api/webhooks/discord — Discord interactions endpoint - POST /api/webhooks/telegram — Telegram updates - POST /api/webhooks/slack — Slack events - GET /api/gateway/discord — Gateway cron for Discord messages ### Standalone mode (src/index.ts) - Long-running process for Docker/PM2 deployment - Auto-detects enabled adapters from env vars - Redis or in-memory state ## Stacked on - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
1. **Broken bot auth (CRITICAL)** — Replaced `Depends(lambda req: ...)` with proper `async def get_bot_api_key(request: Request)` dependency. The lambda pattern caused FastAPI to interpret `req` as a required query parameter, making all bot endpoints return 422. 2. **Timing-safe key comparison** — Switched from `!=` to `hmac.compare_digest()` to prevent timing side-channel attacks. 3. **Removed dead code** — Deleted the unused `verify_bot_api_key` function that had a stub body passing all requests. 4. **TOCTOU race in confirm** — Wrapped `PlatformLink.prisma().create()` in try/except for unique constraint violations. Concurrent requests with different tokens for the same identity now get a clean 409 instead of an unhandled 500. Also: regenerated openapi.json (removes spurious `req` query parameter that was leaking from the broken lambda pattern).
Multi-platform bot service that deploys CoPilot to Discord, Telegram, and Slack from a single codebase using Vercel's Chat SDK. ## What's included ### Core bot (src/bot.ts) - Chat SDK instance with dynamic adapter loading - onNewMention: resolves platform user → AutoGPT account - Unlinked users get a link prompt via the platform-linking API - Subscribed message handler with state management - MVP echo response (CoPilot API integration next) ### Platform API client (src/platform-api.ts) - Calls /api/platform-linking/resolve on every message - Creates link tokens for unlinked users - Checks link token status - Chat session creation and SSE streaming (prepared for CoPilot) ### Serverless routes (src/api/) - POST /api/webhooks/discord — Discord interactions endpoint - POST /api/webhooks/telegram — Telegram updates - POST /api/webhooks/slack — Slack events - GET /api/gateway/discord — Gateway cron for Discord messages ### Standalone mode (src/index.ts) - Long-running process for Docker/PM2 deployment - Auto-detects enabled adapters from env vars - Redis or in-memory state ## Stacked on - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
|
/review |
|
This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request. |
There was a problem hiding this comment.
All 8 specialists have reported. Compiling the final verdict now.
PR #12615 — feat: Platform bot linking API for multi-platform CoPilot
Author: Bentlybro | SHA: a268291 | Status: Draft → dev
Files: routes.py (+628), routes_test.py (+136), rest_api.py (+6), schema.prisma (+47), migration.sql (+44), openapi.json (+274)
🎯 Verdict: REQUEST_CHANGES
What This PR Does
Adds a backend API and database schema for linking external chat platform identities (Discord, Telegram, Slack, Teams, WhatsApp, GitHub, Linear) to AutoGPT user accounts. The flow: a CoPilot bot encounters an unlinked user → generates a one-time token → user clicks a link to confirm → bot can then proxy chat requests as that user. Includes 8 API endpoints (token CRUD, identity resolution, link management, chat proxy) and two new Prisma models (PlatformLink, PlatformLinkToken). Backend-only — no frontend link page exists yet.
Specialist Findings
🛡️ Security ⛔ — Two critical issues found. The bot chat proxy (/chat/stream, /chat/session) accepts any user_id and only checks that the user has some platform link — a single compromised BOT_API_KEY grants impersonation of every linked user across all platforms (routes.py:443-460). Worse, the dev-mode auth bypass fires when PLATFORM_BOT_API_KEY is unset because ENV defaults to "development" (routes.py:57-63) — meaning all bot endpoints are completely unauthenticated if ops forgets one env var. Additionally: no rate limiting on token creation or resolve (enables DB flooding and user enumeration), /tokens/{token}/status leaks internal user_id to any bot-key holder, and TOCTOU race in confirm_link_token can burn a token without creating a link.
🏗️ Architecture /chat/stream) is a coupling bomb — it inline-imports 6 symbols from backend.copilot internals and reimplements the full CoPilot SSE path including Redis stream subscriptions, keepalives, and turn lifecycle (routes.py:395-530). If CoPilot streaming evolves, this breaks silently. The Platform Python enum must stay manually synchronized with Prisma's PlatformType — no codegen, no validation test, guaranteed future desync. Token invalidation via usedAt = now() conflates "cancelled" with "redeemed," causing get_link_token_status to return status: "linked", user_id: null for invalidated tokens — a semantic bug.
⚡ Performance /resolve endpoint is optimal (hits unique index on PlatformLink(platform, platformUserId) — single indexed lookup per message). However, PlatformLinkToken is missing a composite index on (platform, platformUserId) — the update_many in create_link_token and find_first in get_link_token_status will do sequential scans, worsening as the table grows unbounded (no cleanup mechanism for expired tokens). confirm_link_token does 4 sequential DB round-trips where 3 would suffice (the find_first before create is redundant given the unique constraint catch). SSE stream has no maximum connection duration — stuck responses hold connections indefinitely.
schema.prisma:PlatformLinkToken — add @@index([platform, platformUserId])
🧪 Testing ⛔ — Only ~15% of the 628 new lines are covered. The test file validates enums, Pydantic models, and API key checking — zero endpoint-level tests. No tests for: the confirm flow (race conditions, expiry, double-click), the delete authorization check (IDOR), chat proxy endpoints, token expiry rejection, or the production-mode 503 branch when ENV != "development". The repo has established patterns (chat/routes_test.py, otto/routes_test.py) for TestClient-based endpoint testing that this PR should follow. The TestResponseModels class tests that True == True — nearly zero value.
📖 Quality ✅ — Well-structured module with clear section separators and thorough header docstring. Minor items: get_bot_api_key name suggests retrieval not extraction; 5 response models lack docstrings while request models have them; OpenAPI operation IDs are auto-generated sentence slugs (e.g., "postPlatform-linkingCreate a copilot session for a linked user (bot-facing)") — should use explicit operation_id for SDK codegen. Redundant string check at routes.py:277 ("Unique" in str(exc) is already covered by .lower()). Inline imports in bot_chat_stream are intentional but undocumented.
📦 Product link_url pointing to https://platform.agpt.co/link/{token} but no frontend /link/{token} page exists — users clicking the link get a 404 on day one. Error messages are developer-facing with no recovery guidance (e.g., "Token not found" with no "request a new link" prompt). The chat proxy grants the bot indefinite impersonation access with no consent disclosure during linking, no visibility into bot activity, and no easy revoke (the settings UI doesn't exist). PLATFORM_LINK_BASE_URL defaults to production — staging/dev will generate links pointing at prod.
📬 Discussion ✅ — PR is in Draft state, no human reviews submitted (reviewDecision: REVIEW_REQUIRED). Two automated reviews from autogpt-pr-reviewer bot: Round 1 flagged 3 blockers + 8 should-fix items → all addressed in commit 7bf10c6. Round 2 flagged 4 new blockers (broken Depends(lambda), timing attack on key comparison, dead function, TOCTOU race) → all addressed in commit a268291. Author is highly responsive, turning around fixes within 30-60 minutes. CI: 26/27 checks passing (only chromatic skipped — not required). No merge conflicts. Third review round triggered but not yet completed.
🔎 QA ✅ — All 8 new API endpoints are live and correctly serving. Full endpoint-by-endpoint validation confirmed: token creation returns proper response shape, status polling works, auth-protected endpoints return 401 without JWT, input validation rejects invalid platforms/empty IDs/oversized fields. The complete linking flow (create → status → resolve → confirm-requires-auth) works as designed. Frontend is fully functional with no regressions — landing, signup, onboarding, copilot, build, and library pages all render correctly.
Blockers (Must Fix)
-
routes.py:57-63— Dev-mode auth bypass defaults to unauthenticated in production. IfPLATFORM_BOT_API_KEYis unset andENVisn't explicitly set, all bot endpoints are wide open. Invert the default: reject when no key is configured, or require explicitENV=developmentopt-in. (Security + Architect cross-reference) -
routes.py:443-460— Bot can impersonate any linked user with a single API key./chat/streamand/chat/sessionaccept arbitraryuser_idwithout verifying the caller's platform scope. Requireplatform+platform_user_idinBotChatRequestand verify the resolved link matches. (Security) -
routes_test.py— Near-zero endpoint test coverage for 628 lines of route logic. No tests for confirm flow, delete authorization (IDOR), chat proxy, token expiry, or production-mode 503. Must add endpoint-level tests following the repo's establishedTestClientpattern before merge. (Testing)
Should Fix (Follow-up OK)
-
schema.prisma:PlatformLinkToken— Add@@index([platform, platformUserId]). Multiple queries filter on this pair but onlytokenandexpiresAtare indexed. Sequential scans will degrade as the table grows unbounded. (Performance + Architect cross-reference) -
routes.py:395-530— Extract chat proxy fromplatform_linkingmodule. The/chat/streamendpoint couples tightly to 6 CoPilot internals. Should use a service facade or live in a dedicatedcopilot_proxymodule. (Architect) -
routes.py:151-157— Token invalidation conflates "cancelled" with "used." SettingusedAton invalidated tokens causesget_link_token_statusto reportstatus: "linked"withuser_id: null. Add astatusfield or useinvalidatedAt. (Architect + Quality) -
No TTL/cleanup for
PlatformLinkTokentable. Expired tokens accumulate forever. Add a periodic cleanup job. (Performance + Architect) -
routes.py:36—PLATFORM_LINK_BASE_URLdefaults to production URL. Staging/dev environments will generate broken links pointing at prod. Consider requiring explicit config or deriving from request host. (Product) -
No rate limiting on
POST /tokensandPOST /resolve. Enables DB flooding and user enumeration with a compromised key. (Security)
Risk Assessment
Merge risk: HIGH — The dev-mode auth bypass (#1) is a production security hole if env vars aren't perfectly configured. The impersonation scope issue (#2) means a single compromised key compromises all users.
Rollback: EASY — New feature behind new endpoints, no changes to existing functionality. Drop the migration to remove tables.
REVIEW_COMPLETE
PR: #12615
Verdict: REQUEST_CHANGES
Blockers: 3
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
…outes, chat_proxy - Split 628-line routes.py into 4 files under ~300 lines each: models.py (Pydantic models), auth.py (bot API key validation), routes.py (linking routes), chat_proxy.py (bot chat endpoints) - Move all inline imports to top-level - Read PLATFORM_BOT_API_KEY per-request instead of module-level global - Read PLATFORM_LINK_BASE_URL per-request in create_link_token - Use backend Settings().config.enable_auth for dev-mode bypass instead of raw ENV env var - Add Path validation (max_length=64, regex) on token path params - Update test imports to match new module paths
Multi-platform bot service that deploys CoPilot to Discord, Telegram, and Slack from a single codebase using Vercel's Chat SDK. ## What's included ### Core bot (src/bot.ts) - Chat SDK instance with dynamic adapter loading - onNewMention: resolves platform user → AutoGPT account - Unlinked users get a link prompt via the platform-linking API - Subscribed message handler with state management - MVP echo response (CoPilot API integration next) ### Platform API client (src/platform-api.ts) - Calls /api/platform-linking/resolve on every message - Creates link tokens for unlinked users - Checks link token status - Chat session creation and SSE streaming (prepared for CoPilot) ### Serverless routes (src/api/) - POST /api/webhooks/discord — Discord interactions endpoint - POST /api/webhooks/telegram — Telegram updates - POST /api/webhooks/slack — Slack events - GET /api/gateway/discord — Gateway cron for Discord messages ### Standalone mode (src/index.ts) - Long-running process for Docker/PM2 deployment - Auto-detects enabled adapters from env vars - Redis or in-memory state ## Stacked on - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)











Summary
Adds the account linking system that enables CoPilot to work across multiple chat platforms (Discord, Telegram, Slack, Teams, WhatsApp, GitHub, Linear) using the Vercel Chat SDK.
Problem
To deploy CoPilot as bots on Discord, Telegram, Slack, Teams, etc., we need a way to map platform user identities to AutoGPT accounts. A Discord user is
353922987235213313, the same person on Telegram is123456789— we need to know they're the same AutoGPT user.Solution
Universal link-back flow
Instead of implementing OAuth per-platform, users link via AutoGPT's existing auth:
This works identically on every platform — no platform-specific OAuth needed.
What's added
Database:
PlatformTypeenum (DISCORD, TELEGRAM, SLACK, TEAMS, WHATSAPP, GITHUB, LINEAR)PlatformLinkmodel — maps (platform, platform_user_id) → AutoGPT userPlatformLinkTokenmodel — one-time link tokens with expiryAPI endpoints (
/api/platform-linking):POST /tokensGET /tokens/{token}/statusPOST /resolvePOST /tokens/{token}/confirmGET /linksDELETE /links/{link_id}Context
This is part of the "bot anywhere" initiative — deploying CoPilot to every major chat platform via Vercel's Chat SDK. The Chat SDK handles all the messaging/transport; this PR handles the identity layer.
TODO (follow-up PRs)
/link/{token})copilot-multibot)