feat(gateway): add Microsoft Teams source#139
Conversation
yordis
commented
Apr 28, 2026
- Teams events need the same reliable ingress path as the other collaboration sources.
- Graph subscriptions need edge validation so downstream consumers can rely on trusted notification flow.
- Rich Teams notifications need to retain validation context for consumers that decrypt resource data.
PR SummaryMedium Risk Overview Introduces a new Updates Docker/compose and gateway docs/examples, wires the new source into gateway config resolution, routing, and stream provisioning, and adds unit tests covering config validation and webhook publish behavior. Reviewed by Cursor Bugbot for commit 0dc0451. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughAdds a new Microsoft Teams webhook source crate and integrates it into the Trogon gateway: configuration parsing, HTTP routing (POST /microsoft-teams/webhook), JetStream stream provisioning, client-state validation, and publishing logic with tests and docs updates. Changes
Sequence DiagramsequenceDiagram
actor Client as Microsoft Teams<br/>(Graph)
participant Gateway as Trogon Gateway<br/>(HTTP Server)
participant Handler as Teams Handler
participant Validator as ClientState Validator
participant Publisher as JetStream Publisher
Client->>Gateway: POST /microsoft-teams/webhook (payload / ?validationToken)
Gateway->>Handler: route to Teams handler
Handler->>Handler: parse JSON notification collection
Handler->>Validator: verify clientState (if required)
alt validationToken query present
Handler-->>Client: 200 OK (plain text validationToken)
else clientState missing/invalid
Validator-->>Handler: invalid
Handler-->>Client: 401 Unauthorized
else clientState valid
Validator-->>Handler: valid
Handler->>Handler: compute subject (prefix.resource_kind.change_type) or unroutable
alt unroutable
Handler->>Publisher: publish to <prefix>.unroutable (with reject header)
else routable
Handler->>Publisher: publish to <prefix>.<resource_kind>.<change_type> with headers
end
Publisher-->>Handler: publish result
alt publish success
Handler-->>Client: 202 Accepted
else publish failure
Handler-->>Client: 500 Internal Server Error
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 0/1 reviews remaining, refill in 21 minutes and 30 seconds.Comment |
Code Coverage SummaryDetailsDiff against mainResults for commit: 0dc0451 Minimum allowed coverage is ♻️ This comment has been updated with latest results |
be5f9ec to
f1b218a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
rsworkspace/crates/trogon-gateway/src/config.rs (1)
328-342: Rename this to an explicit boundary type.This struct is the untrusted
confiqueinput, but it shares the same name as the validated domain config returned later fromresolve_microsoft_teams. Renaming it to something likeMicrosoftTeamsConfigInputwould make the one-way conversion boundary much clearer.As per coding guidelines, "Untrusted input must use distinct
*Input/*Wire/*Requesttypes. Convert those boundary types into domain types exactly once."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rsworkspace/crates/trogon-gateway/src/config.rs` around lines 328 - 342, The struct MicrosoftTeamsConfig is the untrusted confique input but is named identically to the validated domain config returned by resolve_microsoft_teams; rename this input type to an explicit boundary name (e.g., MicrosoftTeamsConfigInput or MicrosoftTeamsConfigWire) and update all places that construct or accept the unvalidated config (including the derive(Config) declaration) so that resolve_microsoft_teams converts the new MicrosoftTeamsConfigInput into the domain config exactly once, keeping the domain config type name unchanged.rsworkspace/crates/trogon-source-microsoft-teams/src/server.rs (2)
390-412: Parse resource paths by segment instead of substring matching.The
contains("messages")/contains("teams")checks will also match opaque IDs or other path text, so the fallback router can classify some payloads under the wrong resource kind. Matching normalized path segments would keep routing deterministic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rsworkspace/crates/trogon-source-microsoft-teams/src/server.rs` around lines 390 - 412, The current resource_kind_from_resource_path uses substring contains(...) which can misclassify opaque IDs; change it to parse the path into normalized segments and match exact segment names (e.g., split the input by '/' and ignore empty segments, lowercase each segment) then check segment == "messages", "members", "channels", "chats", "recordings", "transcripts", "presences", "onlinemeetings", "teams" and return the corresponding Some("...") for the first exact match; update resource_kind_from_resource_path to iterate segments (instead of contains) so routing is deterministic and not triggered by substrings inside IDs.
28-33: Introduce a typed wire model for each notification instead ofVec<Value>.Keeping the collection contents as raw
serde_json::Valuemeans extraction, mutation, and validation are spread across the handler. ANotificationWiretype plus one conversion step would make the boundary explicit and simplify everything after deserialization.As per coding guidelines, "Untrusted input must use distinct
*Input/*Wire/*Requesttypes. Convert those boundary types into domain types exactly once."Also applies to: 209-219
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rsworkspace/crates/trogon-source-microsoft-teams/src/server.rs` around lines 28 - 33, Replace the untyped Vec<Value> in ChangeNotificationCollection with a concrete wire type (e.g., define a new struct NotificationWire / NotificationWireInput that models the JSON fields you expect) and change ChangeNotificationCollection.value: Vec<Value> -> Vec<NotificationWire>; implement TryFrom<NotificationWire> (or a dedicated conversion function) to convert/validate the wire type into your domain Notification type in a single place, and update the code paths that deserialize/process notifications (the handler that currently iterates over ChangeNotificationCollection.value) to first deserialize into ChangeNotificationCollection, then convert each NotificationWire -> Notification using the TryFrom/convert function; keep the existing serde attributes (e.g., validation_tokens rename) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@rsworkspace/crates/trogon-gateway/README.md`:
- Around line 79-80: Update the README bullets so the default lists match the
actual defaults in rsworkspace/crates/trogon-gateway/src/config.rs: add
"twitter" to the TROGON_SOURCE_<SOURCE>_SUBJECT_PREFIX defaults and add "NOTION"
and "SENTRY" to the TROGON_SOURCE_<SOURCE>_STREAM_NAME defaults, ensuring the
exact casing/entries mirror those in config.rs for the symbols
TROGON_SOURCE_<SOURCE>_SUBJECT_PREFIX and TROGON_SOURCE_<SOURCE>_STREAM_NAME.
---
Nitpick comments:
In `@rsworkspace/crates/trogon-gateway/src/config.rs`:
- Around line 328-342: The struct MicrosoftTeamsConfig is the untrusted confique
input but is named identically to the validated domain config returned by
resolve_microsoft_teams; rename this input type to an explicit boundary name
(e.g., MicrosoftTeamsConfigInput or MicrosoftTeamsConfigWire) and update all
places that construct or accept the unvalidated config (including the
derive(Config) declaration) so that resolve_microsoft_teams converts the new
MicrosoftTeamsConfigInput into the domain config exactly once, keeping the
domain config type name unchanged.
In `@rsworkspace/crates/trogon-source-microsoft-teams/src/server.rs`:
- Around line 390-412: The current resource_kind_from_resource_path uses
substring contains(...) which can misclassify opaque IDs; change it to parse the
path into normalized segments and match exact segment names (e.g., split the
input by '/' and ignore empty segments, lowercase each segment) then check
segment == "messages", "members", "channels", "chats", "recordings",
"transcripts", "presences", "onlinemeetings", "teams" and return the
corresponding Some("...") for the first exact match; update
resource_kind_from_resource_path to iterate segments (instead of contains) so
routing is deterministic and not triggered by substrings inside IDs.
- Around line 28-33: Replace the untyped Vec<Value> in
ChangeNotificationCollection with a concrete wire type (e.g., define a new
struct NotificationWire / NotificationWireInput that models the JSON fields you
expect) and change ChangeNotificationCollection.value: Vec<Value> ->
Vec<NotificationWire>; implement TryFrom<NotificationWire> (or a dedicated
conversion function) to convert/validate the wire type into your domain
Notification type in a single place, and update the code paths that
deserialize/process notifications (the handler that currently iterates over
ChangeNotificationCollection.value) to first deserialize into
ChangeNotificationCollection, then convert each NotificationWire -> Notification
using the TryFrom/convert function; keep the existing serde attributes (e.g.,
validation_tokens rename) intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 80d9b551-336b-4b85-9de4-2fb313279d28
⛔ Files ignored due to path filters (1)
rsworkspace/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
devops/docker/compose/.env.exampledevops/docker/compose/services/trogon-gateway/README.mdrsworkspace/Cargo.tomlrsworkspace/crates/trogon-gateway/Cargo.tomlrsworkspace/crates/trogon-gateway/README.mdrsworkspace/crates/trogon-gateway/src/config.rsrsworkspace/crates/trogon-gateway/src/http.rsrsworkspace/crates/trogon-gateway/src/streams.rsrsworkspace/crates/trogon-source-microsoft-teams/Cargo.tomlrsworkspace/crates/trogon-source-microsoft-teams/src/client_state.rsrsworkspace/crates/trogon-source-microsoft-teams/src/config.rsrsworkspace/crates/trogon-source-microsoft-teams/src/constants.rsrsworkspace/crates/trogon-source-microsoft-teams/src/lib.rsrsworkspace/crates/trogon-source-microsoft-teams/src/server.rs
0e7aedd to
9e730bd
Compare
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
9e730bd to
9cfae59
Compare
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 480f37c. Configure here.
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
rsworkspace/crates/trogon-gateway/README.md (1)
42-61:⚠️ Potential issue | 🟡 MinorKeep
incidentioin the top-level gateway docs.The route table, required-setting table, and sample TOML now list Teams/Notion/Sentry, but
incidentiois still missing from all three. That leaves this README out of sync withrsworkspace/crates/trogon-gateway/src/config.rsanddevops/docker/compose/services/trogon-gateway/README.md, which both still document the source.📝 Suggested doc fix
| Twitter/X | `/twitter/webhook` | | GitLab | `/gitlab/webhook` | +| incident.io | `/incidentio/webhook` | | Linear | `/linear/webhook` | | Microsoft Teams | `/microsoft-teams/webhook` | | Notion | `/notion/webhook` | | Sentry | `/sentry/webhook` | @@ | Twitter/X | `TROGON_SOURCE_TWITTER_CONSUMER_SECRET` | | GitLab | `TROGON_SOURCE_GITLAB_WEBHOOK_SECRET` | +| incident.io | `TROGON_SOURCE_INCIDENTIO_SIGNING_SECRET` | | Linear | `TROGON_SOURCE_LINEAR_WEBHOOK_SECRET` | | Microsoft Teams | `TROGON_SOURCE_MICROSOFT_TEAMS_CLIENT_STATE` | | Notion | `TROGON_SOURCE_NOTION_VERIFICATION_TOKEN` | | Sentry | `TROGON_SOURCE_SENTRY_CLIENT_SECRET` | @@ [sources.gitlab] webhook_secret = "gitlab-secret" +[sources.incidentio] +signing_secret = "whsec_..." + [sources.linear] webhook_secret = "linear-secret"Also applies to: 129-136
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rsworkspace/crates/trogon-gateway/README.md` around lines 42 - 61, The README is missing the incidentio source entries—update the route table, the "Source enablement" required-setting table, and the sample TOML to include incidentio so the docs match rsworkspace/crates/trogon-gateway/src/config.rs and devops/docker/compose/services/trogon-gateway/README.md; specifically add an incidentio row in the route table (e.g., `/incidentio/webhook`), add the corresponding required env var name for incidentio in the "Source enablement" table (the same variable used by config.rs), and include the incidentio settings in the sample TOML block so all three doc locations list incidentio consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@rsworkspace/crates/trogon-gateway/src/config.rs`:
- Around line 328-342: Rename the local unvalidated struct MicrosoftTeamsConfig
to a boundary/input type (e.g., MicrosoftTeamsConfigInput) and update all places
that construct or reference it (including resolve_microsoft_teams and the other
occurrences called out around lines 969-1053) so that the validated domain type
trogon_source_microsoft_teams::MicrosoftTeamsConfig remains distinct; update
struct name in the #[derive(Config)] definition and change any variable
bindings, function parameters, and conversions that map this input type into the
validated trogon_source_microsoft_teams::MicrosoftTeamsConfig to use the new
MicrosoftTeamsConfigInput name to make the input→domain conversion explicit.
In `@rsworkspace/crates/trogon-source-microsoft-teams/src/server.rs`:
- Around line 219-223: The loop currently early-returns a server error on the
first failing publish which causes a 500 even when prior notifications
succeeded; change the logic in the loop over notifications that calls
publish_notification(&state, notification, &metadata).await so it does not
return immediately on status.is_server_error(). Instead, collect
per-notification outcomes (e.g., a boolean or Vec of statuses), continue
publishing remaining notifications, and after the loop decide the HTTP response:
if all succeeded return success (200/202), if some succeeded and some failed
return a multi-status/partial success (207 or 202 with a per-item report), and
only return a 500 if none succeeded; ensure publish_notification and any
downstream publish_event/idempotency behavior are preserved.
- Around line 97-100: The current change_type_token implementation only ensures
the string is a valid NATS token but does not verify it is a known Graph
changeType; update it to parse/convert the incoming boundary string into a
domain value object (e.g., a new ChangeType enum or struct) and return
RejectReason::InvalidChange for any unrecognized values instead of accepting
arbitrary strings; then build the NatsToken from the canonical domain value
(e.g., ChangeType::as_str()) so the routing code that uses change_type_token
(and the publish path around the former routing at lines ~249-265) only ever
sees validated, domain-specific change types. Ensure conversion is done once at
the boundary in change_type_token (or an explicit from_wire/from_input helper)
and propagate the domain type (or its canonical string) to downstream routing.
---
Outside diff comments:
In `@rsworkspace/crates/trogon-gateway/README.md`:
- Around line 42-61: The README is missing the incidentio source entries—update
the route table, the "Source enablement" required-setting table, and the sample
TOML to include incidentio so the docs match
rsworkspace/crates/trogon-gateway/src/config.rs and
devops/docker/compose/services/trogon-gateway/README.md; specifically add an
incidentio row in the route table (e.g., `/incidentio/webhook`), add the
corresponding required env var name for incidentio in the "Source enablement"
table (the same variable used by config.rs), and include the incidentio settings
in the sample TOML block so all three doc locations list incidentio
consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6299f13f-74a7-4870-95ab-3925d9a9079e
⛔ Files ignored due to path filters (1)
rsworkspace/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
devops/docker/compose/.env.exampledevops/docker/compose/services/trogon-gateway/README.mdrsworkspace/Cargo.tomlrsworkspace/crates/trogon-gateway/Cargo.tomlrsworkspace/crates/trogon-gateway/README.mdrsworkspace/crates/trogon-gateway/src/config.rsrsworkspace/crates/trogon-gateway/src/http.rsrsworkspace/crates/trogon-gateway/src/streams.rsrsworkspace/crates/trogon-source-microsoft-teams/Cargo.tomlrsworkspace/crates/trogon-source-microsoft-teams/src/client_state.rsrsworkspace/crates/trogon-source-microsoft-teams/src/config.rsrsworkspace/crates/trogon-source-microsoft-teams/src/constants.rsrsworkspace/crates/trogon-source-microsoft-teams/src/lib.rsrsworkspace/crates/trogon-source-microsoft-teams/src/server.rs
✅ Files skipped from review due to trivial changes (6)
- rsworkspace/crates/trogon-gateway/Cargo.toml
- rsworkspace/Cargo.toml
- devops/docker/compose/.env.example
- rsworkspace/crates/trogon-source-microsoft-teams/Cargo.toml
- rsworkspace/crates/trogon-source-microsoft-teams/src/lib.rs
- rsworkspace/crates/trogon-source-microsoft-teams/src/config.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- rsworkspace/crates/trogon-gateway/src/http.rs
- rsworkspace/crates/trogon-source-microsoft-teams/src/client_state.rs
- rsworkspace/crates/trogon-source-microsoft-teams/src/constants.rs
- rsworkspace/crates/trogon-gateway/src/streams.rs
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
c1ca26c to
ed76de1
Compare
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
ed76de1 to
0dc0451
Compare
