Repository navigation
feat(automations): Microsoft Teams trigger provider - #6592
Conversation
|
Warning Review limit reached
Next review available in: 11 minutes Limit details: You’ve used all 10 included reviews currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (37)
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 |
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
44e2dfd to
7562e34
Compare
7562e34 to
86a49c0
Compare
Two triggers: "Message in channel" (team › channel, actor, text filter)
and "Channel created" (team, name filter), matched against Graph change
notifications for a tenant the admin consented to.
Shape is fetch-on-notify: the Entra app registration holds application
permissions (ChannelMessage.Read.All, Channel.ReadBasic.All,
Team.ReadBasic.All), consent is the tenant-wide adminconsent endpoint, and
the connection caches an app-only client-credentials token. Two tenant-wide
subscriptions per connection (/teams/getAllMessages, /teams/getAllChannels,
created only, no resource data so no encryption certificate), requested for
two days and renewed by a QStash-signed job once within twelve hours; the
lifecycle route answers reauthorizationRequired / subscriptionRemoved by
forcing that renewal.
Graph gives a notification endpoint three seconds and throttles endpoints
that miss it, so the notify and lifecycle routes only echo the
validationToken handshake, compare clientState in constant time against
the tenant's connection and hand each accepted notification to QStash; a
signed process route re-checks the connection, GETs the message or channel
from Graph, records it in automation_events (idempotent on connection +
resource id) and dispatches through the shared dispatchMatchingTriggers.
After consent the admin is sent through an OpenID sign-in so their Entra
object id lands in user_identities (provider microsoft_teams, scoped to the
tenant), which is what `me` on a Teams trigger resolves through.
Desktop: providers/microsoftTeams (grammar, renderer, useTeamsOptions under
options.microsoftTeams), registry line, useProviderOptions line. tRPC:
integration.microsoftTeams {getConnection, disconnect, listTeams,
listChannels}. Web: the /integrations/microsoft-teams page carrying the
Connect button. Env: MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_SECRET, optional
so every other environment keeps booting.
No migration: the enum values landed in #6581. The consent-callback upsert
names the partial-index predicate (provider <> 'google') so Postgres infers
the org-scoped uniqueness from #6603.
86a49c0 to
f3426fe
Compare
What
Adds
microsoft_teamsas a trigger provider for automations, following the additive seam from #6571. Enum values are already on main (#6581) — no migration.Events (fixed list from the brief):
message_in_channelchannel_createdShape — Google-shaped, fetch-on-notify
connectredirects to Entra'sorganizations/v2.0/adminconsentwith.default;callbackproves consent by acquiring an app-only client-credentials token for the returned tenant (thetenantparam alone is attacker-controlled), upsertsintegration_connections(externalOrgId= tenant id, token cached with expiry,config={tenantId, clientState, subscriptions}), creates the Graph subscriptions, then chains an OpenID auth-code sign-in (identity/callback) so the consenting admin's Entraoidlands inuser_identities(providermicrosoft_teams,externalScopeId= tenant) — that is whatmeresolves through. Declining that leg costs only "Me", not the connection./teams/getAllMessagesand/teams/getAllChannels,changeType: created,includeResourceData: false(no encryption certificate). Requested for 2 days (Graph allows 3 for these), renewed once within 12h byPOST /api/integrations/microsoft-teams/jobs/renew-subscriptions(QStash-signed, same pattern asautomations/evaluate). All subscription writes go through the per-connection advisory lock.?validationToken=astext/plain; otherwise parse the collection, look up the connection by tenant, compareclientStatewithtimingSafeEqual(the only auth Graph offers without resource data), enqueue each accepted notification to QStash (deduped on subscription + changeType + resource), and answer 202. Refused notifications are acknowledged too — they will not verify on retry either./api/integrations/microsoft-teams/process, QStash-signed) does the slow half: re-reads the connection and re-checks clientState, GETs the chatMessage / channel from Graph, records it inautomation_events(idempotent on connection + channel + message id; payload is the fetched resource) and dispatches throughtriggerMatches. For lifecycle items (reauthorizationRequired/subscriptionRemoved) it forces the renew/recreate step. Non-2xx → QStash retries. System messages (messageType != "message", e.g. "X added Y") are skipped.automation-matching/microsoft-teams.ts+ onecase:nullteam/channel scope matches nothing;channel_createdignores channel + actor (its sentence has neither);messageFilteris substring over the message text (HTML stripped) or the new channel's name.MatchContext.microsoftTeams = {teamId, channelId, ownerIds}.providers/microsoftTeams/(grammar, renderer,useTeamsOptions→integration.microsoftTeams.listTeams/listChannels, channels labelled "Team › Channel"), one registry line, oneuseProviderOptionsline. ActorChip offers Anyone/Me only — the shared people list is GitHub ids. (Option keys will move to the provider-namespaced shape once the consolidated seam lands.)/integrations/microsoft-teamspage (copy of Slack's) with Connect/Disconnect and subscription state; card on/integrations.Prerequisites (documented, not yet provisioned)
ChannelMessage.Read.All,Channel.ReadBasic.All,Team.ReadBasic.All(Organization.Read.Alloptional — only the tenant display name). Redirect URIs:{API}/api/integrations/microsoft-teams/callbackand{API}/api/integrations/microsoft-teams/identity/callback.MICROSOFT_CLIENT_ID/MICROSOFT_CLIENT_SECRETin api + trpc env (both optional — unset means the integration is off and nothing else changes)./api/integrations/microsoft-teams/jobs/renew-subscriptionshourly./teams/getAllMessagesis subscribed without?model=→ Graph evaluation mode, capped at 500 messages / app / tenant / month. Flipping to model A (E5 licences) or B (per-message billing) is one constant insubscriptions.ts.Verification
TerminalAgentAutoResumea11y failure on main.?validationToken=abc%20123%2Bxyz→ 200text/plainabc 123+xyz(lifecycle route too); bad JSON 400; unknown tenant refused; wrongclientStaterefused; correctclientStateaccepted (before the queue split, the same request reached the real Graph GET and got the expected 401 on a placeholder token; with the queue, QStash rejects loopback URLs locally, as it does for every other automation route).ingestChannelMessage/ingestChannel, what the process route calls after the Graph GET) with Graph-shaped chatMessage / channel payloads against the real DB: rows recorded with title/url/actor/resourceKey, redelivery deduped, matcher selects 1/2 triggers for a "deploy" message and 1/2 for an "incident-…" channel, 0/2 for non-matching.