Files
mdnanocom 270b1c2592 fix(gchat): accept endpointUrl as a direct-webhook JWT audience (#518)
## Summary

When a Google Chat app's connection setting **Authentication audience**
is set to **HTTP endpoint URL** (Google's recommended option for
HTTP-hosted apps that aren't behind Cloud Run IAM, see [Verify requests
from Google Chat][1]),
the bearer token Google sends is an OIDC ID token whose `aud` is the
endpoint URL — not the GCP project number.
The adapter previously only verified against `googleChatProjectNumber`,
so URL-audience tokens always failed with
`401 Unauthorized` and direct webhooks silently broke for any app
configured this way.

This change makes the adapter verify direct-webhook JWTs against
`googleChatProjectNumber` and/or `endpointUrl`, accepting either when
both are configured (handy for multi-env setups that mix the two modes).
The constructor's fail-closed check accepts an explicit `endpointUrl` as
a valid direct-webhook verifier alongside `googleChatProjectNumber`,
`pubsubAudience`, and `disableSignatureVerification`.

[1]:
https://developers.google.com/workspace/chat/verify-requests-from-chat

## Behavior

| Config | Direct-webhook `aud` accepted |
| ----------------------------------------------------------- |
--------------------------------- |
| `googleChatProjectNumber` only (current behavior) | project number |
| `endpointUrl` only (**new**) | endpoint URL |
| Both `googleChatProjectNumber` and `endpointUrl` (**new**) | either |
| Neither, no `pubsubAudience`, no `disableSignatureVerification` |
constructor throws (unchanged) |

### Security note

Auto-detected endpoint URLs (the value `handleWebhook` falls back to
from the incoming `request.url` when `endpointUrl` is not configured)
are intentionally **not** promoted to verifier status. Treating an
auto-detected URL as a valid audience would let any caller bypass
verification by hitting the bot at a URL of their choice.
A new `endpointUrlIsAudience` flag captures whether the caller
explicitly configured `endpointUrl`, and only that case enables
URL-based verification. A regression test guards this.

## Implementation

- `verifyBearerToken` accepts `string | string[]`
(`OAuth2Client.verifyIdToken`
  already supports both).
- `handleWebhook` builds `directAudiences = [projectNumber,
explicitEndpointUrl].filter(Boolean)` and passes a single string when
only one verifier is configured (to preserve the prior call shape) or an
array when both are.
- Docs updated in `apps/docs/content/adapters/official/google-chat.mdx`:
describe both authentication-audience modes and document `endpointUrl`
as an accepted verifier.
- Changeset: `@chat-adapter/gchat: patch`.

## Test plan

- [x] `pnpm --filter @chat-adapter/gchat test` — 250/250 pass (5 new
tests).
- [x] `pnpm typecheck` — 33/33 tasks pass.
- [x] `pnpm check` (Ultracite/Biome) clean.
- [x] `pnpm konsistent` clean.
- [x] Validated end-to-end against a real Google Chat app configured
with
"HTTP endpoint URL" as authentication audience: webhooks that previously
      401'd now verify and process correctly.

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-15 14:41:15 +10:00
..