Skip to content

feat(googlechat): Google Chat channel integration#148

Open
tuntran wants to merge 3 commits intonextlevelbuilder:mainfrom
tuntran:feat/googlechat-channel
Open

feat(googlechat): Google Chat channel integration#148
tuntran wants to merge 3 commits intonextlevelbuilder:mainfrom
tuntran:feat/googlechat-channel

Conversation

@tuntran
Copy link

@tuntran tuntran commented Mar 11, 2026

Summary

  • Add Google Chat channel with webhook endpoint, OIDC token verification, and emoji reactions
  • Store service account JSON content directly in DB instead of requiring file path on disk
  • Add google_chat to valid channel types allowlist
  • Wire GoogleChat factory into instance loader and config-based channel registration
  • UI fields for GoogleChat channel setup with SA JSON textarea

Convention Fixes (vs PR #135)

  • Add default GroupPolicy "pairing" for DB instances (security parity with Discord/Feishu/Slack)
  • Fix BaseChannel name to use channels.TypeGoogleChat constant ("google_chat")
  • Warn at startup when OIDC verification is disabled (no project_number)
  • Extract resolveServiceAccountJSON to credentials.go (channel.go under 200 lines)
  • Coolify deployment separated out — not included in this PR

Changes

  • internal/channels/googlechat/ — full channel implementation (webhook handler, bot API, factory, types, credentials)
  • internal/config/config_channels.go — add GoogleChatConfig struct with service_account_json field
  • internal/channels/channel.go — add TypeGoogleChat constant
  • cmd/gateway.go — register GoogleChat factory in instance loader
  • cmd/gateway_channels_setup.go — add GoogleChat to config-based channel registration
  • ui/web/src/pages/channels/ — UI fields for GoogleChat channel setup
  • internal/config/config_secrets.go — mark SA JSON as sensitive

Copy link
Contributor

@viettranx viettranx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: Google Chat Channel Integration

Risk Level: Medium | Verdict: Request Changes

Overall the implementation follows existing channel patterns well. However, there are 2 critical security issues and several important items to address before merge.


Critical (must fix before merge)

C1. UI default policy is "open" instead of "pairing"
ui/web/src/pages/channels/channel-schemas.ts — The GoogleChat schema uses inline policy options with default "open" for both group_policy and dm_policy. All other channels use the shared groupPolicyOptions/dmPolicyOptions with default "pairing". This is a security regression — anyone in the Google Workspace can talk to the bot without approval.

Fix: Use the shared groupPolicyOptions/dmPolicyOptions arrays and set default to "pairing", consistent with all other channels.

C2. Config-based path missing default GroupPolicy "pairing"
cmd/gateway_channels_setup.go — The GoogleChat config registration doesn't set a default GroupPolicy: "pairing". When a user leaves the field empty, bot.go will default to "open".

Fix: Add GroupPolicy: "pairing" default in the config-based registration path, matching what Discord/Feishu/Slack do.


Important (should fix)

H1. Dedup goroutine leak potential
channel.go — Each incoming message spawns a new goroutine with go func() { time.Sleep(5*time.Minute); cache.Delete(key) }() for dedup cleanup. Under high traffic this can accumulate thousands of sleeping goroutines.

Fix: Use time.AfterFunc(5*time.Minute, func() { cache.Delete(key) }) instead — it uses the runtime timer heap without blocking a goroutine.

H2. ServiceAccountFile allows arbitrary file reads
credentials.goos.ReadFile(path) with no path validation. This is a potential path traversal vector. Since ServiceAccountJSON inline already works, consider removing the file-based path entirely, or at minimum validate the path is within an expected directory.

H3. Unsafe type assertion
channel.goval.(*reactionState) will panic if the sync.Map contains a value of unexpected type. Use the comma-ok pattern:

rs, ok := val.(*reactionState)
if !ok {
    return
}

Suggestions

M1. No test coverage — There are zero unit tests for this channel. Other channels like Slack have tests. At minimum, consider tests for extractSenderInfo, isBotMentioned, resolveServiceAccountJSON, and the message chunking logic.

M2. ReactionLevel not validated — Invalid values fall through silently. Consider logging a warning for unrecognized levels.

M3. io.ReadAll error ignored in api.go — The error from reading the response body should be checked.

M4. Trailing blank line at end of channel.go — minor cleanup.


Nice work on the overall structure — the modular split (api/bot/channel/handler/factory/types/credentials) is clean and follows the project's file-size guidelines well. Just need to nail the security defaults and the items above before merging. 👍

tuntran added a commit to tuntran/goclaw that referenced this pull request Mar 12, 2026
- Security: default dm_policy and group_policy to "pairing" instead of
  "open" in both UI schema and bot runtime fallback
- Replace goroutine+sleep dedup cleanup with time.AfterFunc to prevent
  goroutine leak
- Remove ServiceAccountFile path support, only keep inline JSON
- Use comma-ok type assertion in removeReaction to prevent panic
- Check io.ReadAll error in API client instead of discarding
- Warn on unrecognized reaction_level value
- Remove trailing blank line in channel.go
- Add unit tests for credentials, handler helpers, and dedup logic
@tuntran tuntran requested a review from viettranx March 12, 2026 03:19
@tuntran
Copy link
Author

tuntran commented Mar 12, 2026

All review feedback addressed in c29b075:

Critical

  • C1 ✅ UI defaults changed to "pairing", using shared
    dmPolicyOptions/groupPolicyOptions
  • C2 ✅ Runtime fallback in bot.go now defaults to "pairing". Note: checked
    Discord/Slack/Feishu — none set GroupPolicy at config registration level in
    gateway_channels_setup.go either; all rely on runtime fallback in bot.go. Kept consistent.

Important

  • H1 ✅ Replaced go func() { time.Sleep(); delete } with time.AfterFunc
  • H2 ✅ Removed ServiceAccountFile entirely — only inline JSON supported now
  • H3 ✅ Comma-ok type assertion in removeReaction

Suggestions

  • M1 ✅ Added unit tests: credentials_test.go, handler_test.go, bot_test.go
    (concurrent dedup, cleanup, helpers)
  • M2 ✅ slog.Warn on unrecognized reaction_level
  • M3 ✅ io.ReadAll error checked in api.go
  • M4 ✅ Trailing blank line removed

Ready for re-review.

tuntran added 3 commits March 13, 2026 03:32
…ad registration

Channels registered after Manager.Reload() were not getting webhook routes mounted,
causing 404 errors for their webhook handlers. Fixed by delegating webhook requests
through Manager's webhookMap instead of mounting static routes at startup.

- Add webhookMap tracking active webhook paths and their Channel handlers
- Add WebhookServeHTTP() to dynamically route webhook requests to registered channels
- Add MountNewWebhookRoutes() to mount handlers for newly registered channels
- Update Reload() to trigger webhook route mounting for new channels
- Add comprehensive unit tests for webhook mounting and delegation

Fixes: Google Chat Add-on returning 404 on /googlechat/events after channel reload
@tuntran tuntran force-pushed the feat/googlechat-channel branch from 019d018 to 6c3cec0 Compare March 12, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants