fix(inbound): use bot owner token for agent gateway callbacks (#254)

* feat(access): add guest chat ACL and simplify bot access

Unify bot chat permissions around owner and guest ACL so public access, whitelist, and blacklist share a single model. Remove unused sharing paths, add searchable platform identity controls, and normalize Feishu identities to stable open_id records.

* fix(web): format access control panel

Include the post-commit formatting changes applied to the access control UI so the branch stays clean and the PR reflects the final rendered layout.

* fix(migrations): drop legacy bot tables before bots

Ensure the init down migration removes bot_members and bot_preauth_keys before dropping bots so full rollback succeeds after the ACL refactor.

* feat(acl): add source-aware chat trigger rules

Support channel-, conversation-, and thread-scoped ACL rules while keeping allow_guest, whitelist, and blacklist compatible. Also expose observed conversation candidates and normalize channel identity rules to their own platform.

* fix(lint): resolve golangci-lint errors after rebase

- Remove unused receivers and parameters in fakeRows/Service methods
- Delete unused makeNoRow helper and toParticipantFields function
- Fix gci/gofumpt formatting

* fix(lint): fix gci import formatting in acl types and handler

* fix(acl): tighten observed group and thread selection (#245)

Use inbox plus persisted messages to discover observed group and thread routes, and lock scope fields after selecting a concrete observed target. This keeps Telegram group candidates visible and prevents contradictory private/group scope edits.

* chore: regenerate sqlc swagger and sdk after rebase onto main

* fix(inbound): use bot owner token for agent gateway callbacks

The inbound channel processor issued a JWT for the chatting user's
identity. When the agent called back into container/MCP endpoints
(e.g. /bots/{id}/tools, /bots/{id}/mcp-stdio), AuthorizeBotAccess
rejected non-owner users with HTTP 403 "bot access denied".

Resolve the bot owner via PolicyService and issue the downstream
token under the owner's identity, consistent with schedule,
heartbeat, and email gateways. The chatting user's identity is
still tracked via SourceChannelIdentityID and identity headers.
This commit is contained in:
BBQ
2026-03-16 23:05:23 +08:00
committed by GitHub
parent 1c19ec1022
commit 68745133b7
+19 -3
View File
@@ -86,6 +86,7 @@ type ChannelInboundProcessor struct {
jwtSecret string
tokenTTL time.Duration
identity *IdentityResolver
policy PolicyService
acl chatACL
observer channel.StreamObserver
ttsService ttsSynthesizer
@@ -121,6 +122,7 @@ func NewChannelInboundProcessor(
jwtSecret: strings.TrimSpace(jwtSecret),
tokenTTL: tokenTTL,
identity: identityResolver,
policy: policyService,
}
}
@@ -370,10 +372,23 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
}
}
// Issue user JWT for downstream calls (MCP, schedule, etc.). For guests use chat token as Bearer.
// Issue bot-owner JWT for downstream calls (MCP tools, schedule, etc.).
// The agent uses this token to call back into the server's container/MCP
// endpoints which require bot-owner or admin access. Using the chatting
// user's identity would cause 403 for non-owner users.
token := ""
if identity.UserID != "" && p.jwtSecret != "" {
signed, _, err := auth.GenerateToken(identity.UserID, p.jwtSecret, p.tokenTTL)
if p.jwtSecret != "" {
tokenUserID := strings.TrimSpace(identity.UserID)
if p.policy != nil {
if ownerID, err := p.policy.BotOwnerUserID(ctx, identity.BotID); err == nil && ownerID != "" {
tokenUserID = ownerID
} else if p.logger != nil {
p.logger.Warn("resolve bot owner for token failed, falling back to caller identity",
slog.String("bot_id", identity.BotID), slog.Any("error", err))
}
}
if tokenUserID != "" {
signed, _, err := auth.GenerateToken(tokenUserID, p.jwtSecret, p.tokenTTL)
if err != nil {
if p.logger != nil {
p.logger.Warn("issue channel token failed", slog.Any("error", err))
@@ -382,6 +397,7 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
token = "Bearer " + signed
}
}
}
if token == "" && chatToken != "" {
token = "Bearer " + chatToken
}