feat(channels): add native Microsoft Teams channel — full E2E implementation#724
Open
olbboy wants to merge 19 commits intonextlevelbuilder:devfrom
Open
feat(channels): add native Microsoft Teams channel — full E2E implementation#724olbboy wants to merge 19 commits intonextlevelbuilder:devfrom
olbboy wants to merge 19 commits intonextlevelbuilder:devfrom
Conversation
…REST API Implements Teams channel following the Feishu webhook-based pattern: - JWT validation with JWKS key cache (OpenID Connect metadata) - Azure AD client credentials flow for reply tokens - SSRF protection: serviceURL validated against known Bot Framework domains - SingleTenant + MultiTenant bot type support - Frontend schemas for channel creation/editing in UI - Docs updated with Teams channel documentation
18 tests covering: config validation, JWT helpers, webhook parsing, bot mention stripping, peer kind detection, serviceURL SSRF protection. Fix: allow trafficmanager.net domains (real Teams serviceURL host).
Teams is config-only (no DB instance factory yet), so registration must happen outside the instanceLoader guard. Extract to separate registerTeamsChannel() function called unconditionally.
Bot Framework Connector tokens (issuer: api.botframework.com) don't carry Azure AD "tid" claim. Only reject when tid is present AND mismatches — prevents false rejection of legitimate Bot Framework requests for SingleTenant bots. Verified via live E2E test with Azure Bot Service.
…indings Fixes from adversarial code review (2 critical, 5 high, 7 medium): CRITICAL: - Validate JWKS URI from OpenID metadata against Microsoft domain allowlist - Restrict trafficmanager.net to smba.trafficmanager.net only (prevent token exfil) HIGH: - Add http.MaxBytesReader (1MB) on webhook body to prevent DoS - Add io.LimitReader (1MB) on JWKS/OIDC fetch responses - Add 5s cooldown on JWKS forced refresh to prevent thundering herd - Add IsRunning() guard on webhook handler (503 after Stop) MEDIUM: - Add jwt.WithLeeway(30s) for clock skew tolerance - Validate tenantID is a valid UUID at construction time - Validate From.ID and Conversation.ID are non-empty before processing - Add GroupPolicy to TeamsConfig + frontend schema - Floor token expiry at 30s to prevent tight retry loop - Check Conversation.ID in conversationUpdate before storing serviceURL
…nnel Round 2 adversarial review fixes: CRITICAL: - Add CheckPolicy() call in handleMessage — DMPolicy/GroupPolicy were configured but never enforced at message time (silent auth bypass) - Move storeServiceURL after policy check (don't cache URLs from rejected senders) HIGH: - Validate BotType enum (reject unknown values like "FooBar") - Move HTTP fetches outside mutex in forceRefreshKeys — prevents blocking all concurrent JWT validations during JWKS refresh MEDIUM: - Disable HTTP redirect following in fetchJSON (defense-in-depth)
Add GOCLAW_TEAMS_BOT_ID, GOCLAW_TEAMS_BOT_PASSWORD, GOCLAW_TEAMS_TENANT_ID env vars matching the pattern of other channels (Telegram, Discord, etc.). Auto-enables Teams channel when bot_id + bot_password are set via env.
…, chunking Phase 2 of Teams channel implementation: 1. Rate limiting + 429 retry (send.go): - Exponential backoff with jitter, 3 retries max - Retry-After header support, capped at 120s - Retries on 429/5xx, fails immediately on 4xx 2. Typing indicator (channel.go, webhook.go): - Reuses typing.Controller with 3s keepalive (Teams auto-expires in 3s) - Starts on inbound message, stops on outbound reply - Cleanup on channel Stop() 3. Markdown sanitization (format.go): - Teams renders markdown natively (textFormat: "markdown") - Strip HTML tags outside code blocks (XSS prevention) - Strip strikethrough markers (inconsistent across platforms) - Convert markdown tables to code fences for monospace 4. Message chunking (format.go, channel.go): - Split at paragraph > line > word > force boundaries - Code fence (```) continuity across chunks - 80KB limit (verified against official Microsoft docs) - Replaces previous 25KB truncation 39 unit tests pass. Verified against official Microsoft Learn documentation.
Bot Framework JWKS response is ~1.8MB (many RSA keys). The previous 1MB io.LimitReader truncated the response, causing json.Decode to fail with "unexpected EOF". Discovered during live E2E testing.
Generate Teams-compatible sideload packages (manifest.json + icons ZIP) from channel config via three surfaces: - CLI: `goclaw teams app-package --name "Bot" -o teams-app.zip` - HTTP: `GET /v1/teams/app-package?name=Bot&bot_id=xxx` - Web UI: download button on Teams channel detail page Core library at internal/channels/teams/appmanifest/ with embedded default icons, PNG validation, Unicode-safe truncation, and v1.19 manifest schema. Includes unit + HTTP handler tests (37 total). Also registers "teams" as valid channel type in instance CRUD handlers and adds i18n keys for en/vi/zh.
…el loading Register teams.Factory in instance loader so DB-created Teams instances load on gateway restart with healthy status. Enables app package download from DB instances. Config-based channel still handles actual messages.
…ld mapping - Deduplicate webhook paths in Manager.WebhookHandlers() to prevent mux.Handle panic when config + DB Teams channels share same path - Read bot_type/tenant_id from both credentials and config in factory (credentials priority, config fallback for UI-created instances) - Add missing "slack", "zalo_personal" to IsDefaultChannelInstance
Author
|
Superseded by comprehensive Teams channel PR combining Phase 1 + Phase 2 + App Package + Factory. |
UI select fields send "inherit" for block_reply when user selects the default option. coerceStringBools only handled "true"/"false", causing JSON unmarshal failure for all channels with inherit selected. Delete the key so it becomes nil (= inherit gateway default).
UI select fields send "true"/"false"/"inherit" as strings for bool config fields (block_reply, etc). Previously only coerced at load time, meaning bad data persisted in DB and broke channel reload. Now: - Export CoerceStringBools for reuse across handlers - Normalize at save time in both HTTP and WS create/update handlers - Keep load-time coercion as fallback for existing bad data - Handle "inherit" → delete key (nil = inherit gateway default)
When agent (LLM) calls config_permissions.grant with a bare chat ID as
scope (e.g. Teams conversation ID), auto-replace with the full
"group:{channel}:{chatID}" format from the caller's context. Prevents
scope mismatch between grant and check, which caused /addwriter to
silently fail on Teams group chats.
When no file writers exist yet for a group scope, allow the first caller instead of denying. Prevents deadlock where no one can grant permissions because no one has permissions. Mirrors Telegram /addwriter bootstrap.
When no file writers are configured for a group, inject a system prompt telling the agent it has full tool access. Prevents LLM from self-restricting based on training assumptions about group permissions.
- CheckFileWriterPermission: handle ListFileWriters error properly — on DB error, fall through to normal permission check instead of granting bootstrap access to all users - Add warning log when duplicate webhook path is skipped - Fix duplicate comment in gateway_channels_setup.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete Microsoft Teams channel implementation for GoClaw — from Azure Bot Framework integration to sideload app package generation.
What's Included
1. Teams Channel Core (
internal/channels/teams/)channel.go— Channel struct,New(),Start()/Stop()lifecycleauth.go— JWT token validation against Microsoft JWKS endpoints (cached, rotated)webhook.go— HTTP webhook handler for incoming Bot Framework activitiessend.go— Outbound message delivery with retry, typing indicators, rate limitingformat.go— LLM markdown → Teams-compatible markdown sanitizationtypes.go— Bot Framework activity types, conversation referencesfactory.go— DB instance factory for InstanceLoader integration2. App Package Generator (
internal/channels/teams/appmanifest/)generate.go—GenerateZIP(opts)→ manifest.json + icons ZIP (v1.19 schema)generate_test.go— 23 unit tests (validation, truncation, Unicode, icons, ZIP structure)3. CLI Command (
cmd/teams_cmd.go)```bash
goclaw teams app-package --name "My Bot" --bot-id UUID -o teams-app.zip
goclaw teams app-package --name "My Bot" --bot-id UUID --stdout > app.zip
```
4. HTTP API (
internal/http/teams_app_package.go)```
GET /v1/teams/app-package?name=Bot&bot_id=UUID
GET /v1/teams/app-package?name=Bot&instance_id=UUID (resolves bot_id from DB)
```
Response:
application/zipwithContent-Disposition: attachment5. Web UI
6. Security & Hardening
Architecture
```
Microsoft Teams → POST /webhooks/teams → JWT validation → webhook handler
→ parse activity → route to agent via message bus → agent response
→ format markdown → chunk if >28KB → send via Bot Framework REST API
```
Config-based channel (backward compat) + DB instance factory (UI-created instances).
Both coexist safely — webhook paths deduplicated in Manager.WebhookHandlers().
Files Changed (50 files, +3844 lines)
Test Coverage
E2E Verification
Tested on live GoClaw instance with real Azure Bot credentials:
Test plan