Skip to content

feat: managed webhook suite for Graph plugins - #575

Open
yuvrxj-afk wants to merge 3 commits into
mainfrom
managed-webhooks
Open

feat: managed webhook suite for Graph plugins#575
yuvrxj-afk wants to merge 3 commits into
mainfrom
managed-webhooks

Conversation

@yuvrxj-afk

@yuvrxj-afk yuvrxj-afk commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Extends #469's BYO webhook machine to managed connections on the Graph plugins (outlook, onedrive, teams, sharepoint). For managed the token lives in the Hub, so the subscribe engines never ran; this arms the subscription at connect and renewal by resolving the token from the Hub, then verifies inbound events via the stored clientState.

  • Connect: processManagedOAuthDelivery fires subscribeAndReport after storing the delivered token (best-effort — a subscribe failure never breaks the connection).
  • Renewal: renewAccounts primes each row by its plugin authType and passes hub, so the managed keyBuilder branch refreshes the token from the Hub before re-subscribe.
  • Verify: managed webhooks read webhook_signature like oauth_2 (the managed guards are removed); sharepoint reads the stored clientState, fail-closed.

Also adds the corsair/hub jest moduleNameMapper the four plugin configs were missing since #571 (any test loading their index.ts failed to resolve corsair/hub without it).

Stacks on #571 (managed authType declaration). Rebases to a clean diff once that merges.

Tests: renewal (managed prime threads authType + hub, expiry forced), connect subscribe (fires after token store + failure swallowed), per-plugin webhook verify + fail-closed. Typecheck clean across corsair + the four plugins.

Summary by CodeRabbit

  • New Features
    • Added managed authentication support across Asana, Dropbox, GitLab, HubSpot, Linear, Notion, OneDrive, Outlook, SharePoint, Slack, Spotify, Teams, and Zoho Mail.
    • Managed access tokens now support automatic refresh through the connected hub.
  • Bug Fixes
    • Improved webhook verification and fail-closed behavior when required security details are missing.
    • Subscription reporting failures no longer interrupt OAuth delivery.
  • Tests
    • Added coverage for managed authentication, token renewal, subscription reporting, and webhook verification.

@mintlify

mintlify Bot commented Aug 2, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
corsair 🟢 Ready View Preview Aug 2, 2026, 11:18 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
www Ready Ready Preview Aug 3, 2026 8:01pm

Request Review

@github-actions github-actions Bot added core Changes in packages/corsair docs Docs / Mintlify / markdown changes plugin Changes inside a plugin package labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Plugin PR scorecard — packages/asana

Check Status Notes
R1 — Scope: one plugin per PR This PR touches: asana, dropbox, gitlab, hubspot, linear, notion, onedrive, outlook, sharepoint, slack, spotify, teams, zohomail
R2 — Tests with assertions
R3 — Description Description section is empty or placeholder
R3 — Linked issue / claim ⚠️ No "Fixes #…" or claim link — add one if this PR has a claim or issue
R4 — Demo video / recording Required in "Screenshots / Demos" before a maintainer reviews

Rules: PLUGIN_PR_RULES.md · re-runs on every push

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Managed authentication is added to Asana, Dropbox, GitLab, HubSpot, Linear, Notion, OneDrive, Outlook, SharePoint, Slack, Spotify, Teams, and Zoho Mail. Corsair renewal and subscription reporting now support managed authentication. Managed webhook verification fails closed when required state is missing.

Changes

Managed authentication across plugins

Layer / File(s) Summary
Managed auth token builders
packages/asana/index.ts, packages/dropbox/index.ts, packages/gitlab/index.ts, packages/hubspot/index.ts, packages/linear/index.ts, packages/notion/index.ts, packages/slack/index.ts, packages/spotify/index.ts, packages/zohomail/index.ts
These plugins register managed authentication, expose managed authType options, validate Hub configuration, retrieve managed access tokens, and attach refresh handling.
Graph plugin auth and webhook paths
packages/onedrive/index.ts, packages/outlook/index.ts, packages/sharepoint/index.ts, packages/teams/index.ts, packages/sharepoint/webhooks/*, packages/*/managed-webhook.test.ts
OneDrive, Outlook, SharePoint, and Teams add managed authentication and client-state verification. SharePoint passes ctx.key to verification and rejects missing client state.
Webhook signing guards
packages/dropbox/index.ts, packages/gitlab/index.ts, packages/zohomail/index.ts
Managed webhook signature resolution now returns explicit errors when managed authentication cannot provide signing material.
Managed subscription and renewal lifecycle
packages/corsair/hub/managed-oauth.ts, packages/corsair/oauth/renewal.ts, packages/corsair/tests/*
Managed OAuth delivery reports subscription status without rejecting delivery when reporting fails. Renewal resolves each plugin authentication type and passes Hub context to key builders.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PluginKeyBuilder
  participant CorsairHub
  participant ManagedRefreshAuth
  participant ManagedOAuthDelivery
  PluginKeyBuilder->>CorsairHub: request managed access token
  CorsairHub-->>PluginKeyBuilder: return access token
  PluginKeyBuilder->>ManagedRefreshAuth: attach refresh authentication
  ManagedOAuthDelivery->>CorsairHub: report or renew subscription
  CorsairHub-->>ManagedOAuthDelivery: return subscription result
Loading

Possibly related PRs

Suggested labels: bot:round-1, bot:round-2, needs-maintainer

Suggested reviewers: devjain32

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the managed webhook support added for the Graph plugins.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch managed-webhooks

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR extends managed OAuth support and managed Graph webhook handling.

  • Resolves and refreshes managed access tokens through the Hub.
  • Starts subscriptions after managed OAuth delivery and primes managed credentials during renewal.
  • Routes persisted SharePoint client state into webhook verification and fails closed when it is unavailable.
  • Adds managed webhook and subscription lifecycle tests plus Jest Hub module mappings.

Confidence Score: 4/5

The SharePoint batched-notification verification failure needs to be fixed before merging.

A batch is accepted after checking only its first notification, allowing subsequent entries with missing or mismatched client state to reach the webhook handler.

Files Needing Attention: packages/sharepoint/webhooks/list-changes.ts and packages/sharepoint/webhooks/types.ts

Security Review

SharePoint webhook verification now uses the stored client state, but batched requests authenticate only the first notification, allowing later entries with invalid client state to pass verification.

Important Files Changed

Filename Overview
packages/corsair/hub/managed-oauth.ts Starts subscription reporting after managed credentials are persisted while preserving successful OAuth delivery if subscription setup fails.
packages/corsair/oauth/renewal.ts Primes renewal using each plugin's configured authentication type and Hub context.
packages/sharepoint/index.ts Loads the static or persisted webhook client state and rejects requests when no verification value exists.
packages/sharepoint/webhooks/list-changes.ts Passes the bound webhook key into verification, but batched requests accept later notifications without validating their client state.
packages/sharepoint/webhooks/types.ts Fails closed when client state is absent but validates only the first notification in a batch.

Sequence Diagram

sequenceDiagram
    participant Hub
    participant Corsair
    participant Plugin
    participant Graph
    Hub->>Corsair: Deliver managed OAuth token
    Corsair->>Corsair: Persist connection credentials
    Corsair->>Plugin: subscribeAndReport
    Plugin->>Hub: Resolve managed access token
    Plugin->>Graph: Create subscription with clientState
    Graph-->>Corsair: Webhook notification
    Corsair->>Corsair: Load persisted clientState as ctx.key
    Corsair->>Plugin: Handle and verify notification
Loading

Reviews (2): Last reviewed commit: "Merge branch 'main' into managed-webhook..." | Re-trigger Greptile

Comment thread packages/sharepoint/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (10)
packages/dropbox/index.ts (1)

385-401: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same duplicated managed branch as the other plugins. See the consolidated comment for the shared helper suggestion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dropbox/index.ts` around lines 385 - 401, Replace the duplicated
managed-auth handling in the Dropbox flow with the shared helper proposed for
the other plugins. Update the branch around getManagedAccessToken and
attachManagedRefreshAuth to delegate context validation, token retrieval,
refresh attachment, and access-token return to that helper while preserving the
existing Dropbox managed-auth behavior.
packages/gitlab/index.ts (1)

922-939: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same duplicated managed branch as the other plugins. See the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/gitlab/index.ts` around lines 922 - 939, The managed-auth branch in
the GitLab flow duplicates the shared implementation used by other plugins.
Replace the inline managedContext construction, getManagedAccessToken call,
refresh attachment, and access-token return in the surrounding GitLab handler
with the consolidated shared managed-auth path, preserving the existing hub
validation and GitLab-specific context values.
packages/hubspot/index.ts (1)

689-706: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Place the managed branch inside the source === 'endpoint' block.

Lines 669-687 gate api_key and oauth_2 on source === 'endpoint'. The managed branch is not gated. The behavior is the same today, because the webhook branch at Lines 653-667 always returns or throws first. Moving the managed branch into the endpoint block keeps the reader from having to prove that. This block is also duplicated across the other plugins; see the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/hubspot/index.ts` around lines 689 - 706, The managed authentication
branch around getManagedAccessToken and attachManagedRefreshAuth should be
nested within the source === 'endpoint' conditional alongside the api_key and
oauth_2 branches. Preserve its existing hub validation, token refresh setup, and
access-token return behavior while making the endpoint-source gate explicit.
packages/zohomail/index.ts (1)

439-456: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same duplicated managed branch as the other plugins. See the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zohomail/index.ts` around lines 439 - 456, Replace the duplicated
managed-auth branch in the zohomail flow with the shared managed-auth helper
used by the other plugins, preserving the existing hub validation, managed
context values, refresh-auth attachment, and access-token return behavior.
packages/slack/index.ts (1)

796-813: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same duplicated managed branch as the other plugins. See the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/slack/index.ts` around lines 796 - 813, Replace the duplicated
managed-auth branch in the Slack flow with the shared managed-auth handling used
by the other plugins. Reuse the existing helper or consolidated abstraction for
validating ctx.hub, building managedContext, attaching refresh auth, and
returning the access token, while preserving Slack’s plugin and tenant context.
packages/spotify/index.ts (1)

613-630: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same duplicated managed branch as the other plugins. See the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/spotify/index.ts` around lines 613 - 630, Consolidate the duplicated
managed-auth branch in the Spotify integration by reusing the shared managed
authentication helper used by the other plugins. Update the flow around
getManagedAccessToken and attachManagedRefreshAuth while preserving hub
validation, managedContext values, refresh-auth attachment, and access-token
return behavior.
packages/asana/index.ts (1)

1104-1121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated managed key branch into one corsair/hub helper.

Eight plugins repeat the same managed branch: check ctx.hub, build { keys, hub, plugin, tenantId }, call getManagedAccessToken, call attachManagedRefreshAuth, return the token. The only differences are the plugin id and the error text. The root cause is that corsair/hub exports the two primitives but no combined helper. Add one exported helper, for example resolveManagedKey(ctx, pluginId): Promise<string>, and call it from each plugin. The Hub-missing error text then stays consistent across plugins.

  • packages/asana/index.ts#L1104-L1121: replace the branch with return resolveManagedKey(ctx, 'asana');.
  • packages/dropbox/index.ts#L385-L401: replace the branch with return resolveManagedKey(ctx, 'dropbox');.
  • packages/gitlab/index.ts#L922-L939: replace the branch with return resolveManagedKey(ctx, 'gitlab');.
  • packages/hubspot/index.ts#L689-L706: replace the branch with return resolveManagedKey(ctx, 'hubspot');, and move it inside the source === 'endpoint' block.
  • packages/notion/index.ts#L404-L421: replace the branch with return resolveManagedKey(ctx, 'notion');.
  • packages/slack/index.ts#L796-L813: replace the branch with return resolveManagedKey(ctx, 'slack');.
  • packages/spotify/index.ts#L613-L630: replace the branch with return resolveManagedKey(ctx, 'spotify');.
  • packages/zohomail/index.ts#L439-L456: replace the branch with return resolveManagedKey(ctx, 'zohomail');.

packages/linear/index.ts at Lines 525-541 keeps its own return, because it prefixes the token with Bearer . It can call the helper and add the prefix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/asana/index.ts` around lines 1104 - 1121, Extract the shared
managed-auth flow into an exported corsair/hub helper such as
resolveManagedKey(ctx, pluginId), consolidating hub validation, managed context
construction, getManagedAccessToken, attachManagedRefreshAuth, and token return
with consistent errors. Replace the managed branches in packages/asana/index.ts
(1104-1121), packages/dropbox/index.ts (385-401), packages/gitlab/index.ts
(922-939), packages/notion/index.ts (404-421), packages/slack/index.ts
(796-813), packages/spotify/index.ts (613-630), and packages/zohomail/index.ts
(439-456) with the corresponding helper call; move the packages/hubspot/index.ts
(689-706) call inside its source === 'endpoint' block. Also update
packages/linear/index.ts (525-541) to use the helper while preserving its Bearer
prefix.
packages/notion/index.ts (1)

404-421: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same duplicated managed branch as the other plugins. See the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/notion/index.ts` around lines 404 - 421, The managed-auth branch in
the Notion integration duplicates the shared implementation used by other
plugins. Replace the inline logic in the managed branch around
getManagedAccessToken and attachManagedRefreshAuth with the established shared
helper or consolidated flow, preserving hub validation and returning the managed
access token.
packages/onedrive/managed-webhook.test.ts (1)

3-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Managed webhook test suites do not cover the endpoint/managed token-retrieval path. Both files test only the webhook key-builder branch; neither tests the source === 'endpoint', authType === 'managed' branch that calls getManagedAccessToken and attachManagedRefreshAuth.

  • packages/onedrive/managed-webhook.test.ts#L3-L28: add a test that mocks corsair/hub's getManagedAccessToken/attachManagedRefreshAuth and asserts the OneDrive keyBuilder returns the resolved access token and throws when ctx.hub is missing, exercising packages/onedrive/index.ts Lines 912-930.
  • packages/outlook/managed-webhook.test.ts#L3-L28: add the equivalent test for the Outlook keyBuilder, exercising packages/outlook/index.ts Lines 679-697.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/onedrive/managed-webhook.test.ts` around lines 3 - 28, The managed
webhook tests cover only stored clientState and omit endpoint managed-token
retrieval. In packages/onedrive/managed-webhook.test.ts lines 3-28, add tests
mocking corsair/hub getManagedAccessToken and attachManagedRefreshAuth to verify
the OneDrive keyBuilder returns the resolved token and rejects when ctx.hub is
missing; add the equivalent coverage in packages/outlook/managed-webhook.test.ts
lines 3-28 for the Outlook keyBuilder.
packages/onedrive/index.ts (1)

912-930: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Managed-auth key-builder wiring is duplicated across three Graph plugins. Each plugin repeats the same Hub-config check, managedContext construction, getManagedAccessToken call, and attachManagedRefreshAuth attachment, differing only by the plugin literal (and, for SharePoint, an extra source === 'endpoint' guard). Extract this into a shared helper in corsair/hub, consistent with how getManagedAccessToken and attachManagedRefreshAuth are already centralized there.

  • packages/onedrive/index.ts#L912-L930: replace this block with a call to the shared helper, passing 'onedrive' as the plugin id.
  • packages/outlook/index.ts#L679-L697: replace this block with a call to the shared helper, passing 'outlook' as the plugin id.
  • packages/sharepoint/index.ts#L1472-L1490: replace this block with a call to the shared helper, passing 'sharepoint' as the plugin id, preserving the source === 'endpoint' guard around the call site.
♻️ Proposed shared helper (e.g. in corsair/hub)
export async function resolveManagedKeyBuilderToken(
	pluginId: string,
	ctx: { hub?: unknown; keys: unknown; tenantId?: unknown },
): Promise<string> {
	if (!ctx.hub) {
		throw new Error(
			`[auth-missing:${pluginId}:managed]: Hub config is required for managed auth. Pass hub: { ... } to createCorsair().`,
		);
	}
	const managedContext = {
		keys: ctx.keys,
		hub: ctx.hub,
		plugin: pluginId,
		tenantId: ctx.tenantId,
	};
	const result = await getManagedAccessToken(managedContext);
	await attachManagedRefreshAuth(ctx as Record<string, unknown>, managedContext);
	return result.accessToken;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/onedrive/index.ts` around lines 912 - 930, The managed-auth flow is
duplicated across the Graph plugins. Add a shared helper in corsair/hub that
validates hub configuration, builds the managed context, resolves the token,
attaches refresh auth, and returns the access token; then replace the blocks in
packages/onedrive/index.ts lines 912-930 and packages/outlook/index.ts lines
679-697 with calls using their respective plugin IDs, and replace
packages/sharepoint/index.ts lines 1472-1490 with the helper call using
“sharepoint” while preserving the existing source === 'endpoint' guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dropbox/index.ts`:
- Around line 304-306: Update the webhook authentication check around authType
in the Dropbox integration to report the active authentication type when
throwing AuthMissingError, including managed instead of always naming oauth_2.
Preserve the existing error path for missing webhookSecret and align its message
behavior with the corresponding Zoho Mail implementation.

In `@packages/linear/index.ts`:
- Around line 525-541: Update the Linear request flow around
makeLinearRequest(ctx.key) to add a 401 retry path that invokes
ctx._refreshAuth, obtains the refreshed token, and retries the request using
Bearer ${freshToken}. Ensure both managed and OAuth2 authentication paths use
this wrapper instead of calling makeLinearRequest directly, while preserving the
existing initial-token behavior.

In `@packages/sharepoint/index.ts`:
- Around line 1468-1471: Update the comment above the source check near the
managed endpoint calls to state that webhook handling is already terminal and
the source condition is retained only as defensive belt-and-suspenders
protection. Remove the outdated explanation about webhook fall-through, while
preserving the existing source check and endpoint behavior.

In `@packages/zohomail/index.ts`:
- Around line 346-350: Update the managed branch in the webhook authentication
flow to allow managed connections to resolve webhook_signature, removing or
bypassing the error thrown for ctx.authType === 'managed'. Preserve webhook key
resolution for the handshake and subsequent managed deliveries, including the
persisted x-hook-secret from set_webhook_signature.

---

Nitpick comments:
In `@packages/asana/index.ts`:
- Around line 1104-1121: Extract the shared managed-auth flow into an exported
corsair/hub helper such as resolveManagedKey(ctx, pluginId), consolidating hub
validation, managed context construction, getManagedAccessToken,
attachManagedRefreshAuth, and token return with consistent errors. Replace the
managed branches in packages/asana/index.ts (1104-1121),
packages/dropbox/index.ts (385-401), packages/gitlab/index.ts (922-939),
packages/notion/index.ts (404-421), packages/slack/index.ts (796-813),
packages/spotify/index.ts (613-630), and packages/zohomail/index.ts (439-456)
with the corresponding helper call; move the packages/hubspot/index.ts (689-706)
call inside its source === 'endpoint' block. Also update
packages/linear/index.ts (525-541) to use the helper while preserving its Bearer
prefix.

In `@packages/dropbox/index.ts`:
- Around line 385-401: Replace the duplicated managed-auth handling in the
Dropbox flow with the shared helper proposed for the other plugins. Update the
branch around getManagedAccessToken and attachManagedRefreshAuth to delegate
context validation, token retrieval, refresh attachment, and access-token return
to that helper while preserving the existing Dropbox managed-auth behavior.

In `@packages/gitlab/index.ts`:
- Around line 922-939: The managed-auth branch in the GitLab flow duplicates the
shared implementation used by other plugins. Replace the inline managedContext
construction, getManagedAccessToken call, refresh attachment, and access-token
return in the surrounding GitLab handler with the consolidated shared
managed-auth path, preserving the existing hub validation and GitLab-specific
context values.

In `@packages/hubspot/index.ts`:
- Around line 689-706: The managed authentication branch around
getManagedAccessToken and attachManagedRefreshAuth should be nested within the
source === 'endpoint' conditional alongside the api_key and oauth_2 branches.
Preserve its existing hub validation, token refresh setup, and access-token
return behavior while making the endpoint-source gate explicit.

In `@packages/notion/index.ts`:
- Around line 404-421: The managed-auth branch in the Notion integration
duplicates the shared implementation used by other plugins. Replace the inline
logic in the managed branch around getManagedAccessToken and
attachManagedRefreshAuth with the established shared helper or consolidated
flow, preserving hub validation and returning the managed access token.

In `@packages/onedrive/index.ts`:
- Around line 912-930: The managed-auth flow is duplicated across the Graph
plugins. Add a shared helper in corsair/hub that validates hub configuration,
builds the managed context, resolves the token, attaches refresh auth, and
returns the access token; then replace the blocks in packages/onedrive/index.ts
lines 912-930 and packages/outlook/index.ts lines 679-697 with calls using their
respective plugin IDs, and replace packages/sharepoint/index.ts lines 1472-1490
with the helper call using “sharepoint” while preserving the existing source ===
'endpoint' guard.

In `@packages/onedrive/managed-webhook.test.ts`:
- Around line 3-28: The managed webhook tests cover only stored clientState and
omit endpoint managed-token retrieval. In
packages/onedrive/managed-webhook.test.ts lines 3-28, add tests mocking
corsair/hub getManagedAccessToken and attachManagedRefreshAuth to verify the
OneDrive keyBuilder returns the resolved token and rejects when ctx.hub is
missing; add the equivalent coverage in packages/outlook/managed-webhook.test.ts
lines 3-28 for the Outlook keyBuilder.

In `@packages/slack/index.ts`:
- Around line 796-813: Replace the duplicated managed-auth branch in the Slack
flow with the shared managed-auth handling used by the other plugins. Reuse the
existing helper or consolidated abstraction for validating ctx.hub, building
managedContext, attaching refresh auth, and returning the access token, while
preserving Slack’s plugin and tenant context.

In `@packages/spotify/index.ts`:
- Around line 613-630: Consolidate the duplicated managed-auth branch in the
Spotify integration by reusing the shared managed authentication helper used by
the other plugins. Update the flow around getManagedAccessToken and
attachManagedRefreshAuth while preserving hub validation, managedContext values,
refresh-auth attachment, and access-token return behavior.

In `@packages/zohomail/index.ts`:
- Around line 439-456: Replace the duplicated managed-auth branch in the
zohomail flow with the shared managed-auth helper used by the other plugins,
preserving the existing hub validation, managed context values, refresh-auth
attachment, and access-token return behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 223a32d6-b17a-4784-9229-4bc46627ded9

📥 Commits

Reviewing files that changed from the base of the PR and between 839b807 and 20ef459.

📒 Files selected for processing (27)
  • packages/asana/index.ts
  • packages/corsair/hub/managed-oauth.ts
  • packages/corsair/oauth/renewal.ts
  • packages/corsair/tests/managed-oauth-report.test.ts
  • packages/corsair/tests/subscription-renewal.test.ts
  • packages/dropbox/index.ts
  • packages/gitlab/index.ts
  • packages/hubspot/index.ts
  • packages/linear/index.ts
  • packages/notion/index.ts
  • packages/onedrive/index.ts
  • packages/onedrive/jest.config.cjs
  • packages/onedrive/managed-webhook.test.ts
  • packages/outlook/index.ts
  • packages/outlook/jest.config.cjs
  • packages/outlook/managed-webhook.test.ts
  • packages/sharepoint/index.ts
  • packages/sharepoint/jest.config.cjs
  • packages/sharepoint/managed-webhook.test.ts
  • packages/sharepoint/webhooks/list-changes.ts
  • packages/sharepoint/webhooks/types.ts
  • packages/slack/index.ts
  • packages/spotify/index.ts
  • packages/teams/index.ts
  • packages/teams/jest.config.cjs
  • packages/teams/managed-webhook.test.ts
  • packages/zohomail/index.ts

Comment thread packages/dropbox/index.ts
Comment thread packages/linear/index.ts
Comment on lines +525 to +541
if (ctx.authType === 'managed') {
if (!ctx.hub) {
throw new Error(
'[auth-missing:linear:managed]: Hub config is required for managed auth. Pass hub: { ... } to createCorsair().',
);
}

const managedContext = {
keys: ctx.keys,
hub: ctx.hub,
plugin: 'linear',
tenantId: ctx.tenantId,
};

const result = await getManagedAccessToken(managedContext);
await attachManagedRefreshAuth(ctx, managedContext);
return `Bearer ${result.accessToken}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the consumer of _refreshAuth and check how the returned value is applied.
rg -nP --type=ts -C6 '_refreshAuth' packages/corsair

Repository: corsairdev/corsair

Length of output: 1373


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- all _refreshAuth references ---'
rg -n -C8 --glob '*.{ts,tsx,js,jsx}' '_refreshAuth' .

printf '%s\n' '--- managed auth implementation ---'
cat -n packages/corsair/hub/managed-auth.ts | sed -n '1,180p'

printf '%s\n' '--- Linear keyBuilder context and related auth paths ---'
cat -n packages/linear/index.ts | sed -n '470,555p'

printf '%s\n' '--- auth retry and request-header code candidates ---'
rg -n -C8 --glob '*.{ts,tsx,js,jsx}' '401|retry|Authorization|keyBuilder|authType' packages/corsair packages/linear

Repository: corsairdev/corsair

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- exact _refreshAuth source lines ---'
python3 - <<'PY'
from pathlib import Path
for path in Path('.').rglob('*'):
    if not path.is_file() or any(part in {'node_modules', '.git', 'dist', 'build'} for part in path.parts):
        continue
    try:
        text = path.read_text()
    except (UnicodeDecodeError, OSError):
        continue
    for number, line in enumerate(text.splitlines(), 1):
        if '_refreshAuth' in line:
            print(f'{path}:{number}:{line}')
PY

printf '%s\n' '--- endpoint binding retry flow ---'
cat -n packages/corsair/core/endpoints/bind.ts | sed -n '150,275p'

printf '%s\n' '--- key builder type declarations ---'
rg -n -C10 'CorsairKeyBuilder|KeyBuilder' packages/corsair packages/linear --glob '*.ts' | head -n 300

printf '%s\n' '--- Linear auth code ---'
cat -n packages/linear/index.ts | sed -n '490,550p'

printf '%s\n' '--- managed auth code ---'
cat -n packages/corsair/hub/managed-auth.ts | sed -n '105,145p'

Repository: corsairdev/corsair

Length of output: 35740


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- Linear refresh consumer ---'
rg -n -C12 '_refreshAuth|Authorization|Bearer|fetch' packages/linear/client.ts packages/linear --glob '*.ts' | head -n 400

printf '%s\n' '--- representative refresh consumers ---'
for file in packages/googlecalendar/client.ts packages/dropbox/client.ts packages/googlesheets/client.ts packages/github/client.ts; do
  if [ -f "$file" ]; then
    echo "--- $file ---"
    rg -n -C8 '_refreshAuth|Authorization|Bearer' "$file"
  fi
done

printf '%s\n' '--- context passed to endpoint calls ---'
cat -n packages/corsair/core/endpoints/bind.ts | sed -n '255,345p'

Repository: corsairdev/corsair

Length of output: 21214


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- all Linear client call sites ---'
rg -n -C6 'makeLinearRequest|makeAuthenticated|ctx\.key|LinearAuthContext' packages/linear --glob '*.ts'

printf '%s\n' '--- complete Linear client request helpers ---'
cat -n packages/linear/client.ts | sed -n '100,180p'

printf '%s\n' '--- Linear endpoint request usage ---'
rg -n -C10 'makeLinear|ctx\.key|client' packages/linear/endpoints packages/linear --glob '*.ts' | head -n 500

Repository: corsairdev/corsair

Length of output: 50374


Wire Linear requests through a 401 refresh path.

Linear endpoints call makeLinearRequest(ctx.key) directly, so no code reads ctx._refreshAuth. Managed and OAuth2 requests do not retry after a 401. Add a retry wrapper and pass the refreshed token as Bearer ${freshToken}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/linear/index.ts` around lines 525 - 541, Update the Linear request
flow around makeLinearRequest(ctx.key) to add a 401 retry path that invokes
ctx._refreshAuth, obtains the refreshed token, and retries the request using
Bearer ${freshToken}. Ensure both managed and OAuth2 authentication paths use
this wrapper instead of calling makeLinearRequest directly, while preserving the
existing initial-token behavior.

Comment thread packages/sharepoint/index.ts Outdated
Comment on lines +346 to +350
if (ctx.authType === 'managed') {
throw new Error(
'[auth-missing:zohomail:managed]: webhook signature is not available in managed mode',
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether webhook_signature is written for managed connections.
rg -nP --type=ts -C5 'set_webhook_signature|x-hook-secret' packages

Repository: corsairdev/corsair

Length of output: 30245


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- relevant zohomail implementation ---'
sed -n '300,380p' packages/zohomail/index.ts
printf '%s\n' '--- handshake handler ---'
cat -n packages/zohomail/webhooks/challenge.ts
printf '%s\n' '--- authType and key-manager handling ---'
rg -n -C5 --type=ts 'authType|managed|webhook_signature|get_webhook_signature|set_webhook_signature' packages/zohomail packages/corsair/core packages/corsair

Repository: corsairdev/corsair

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- managed Zoho webhook coverage ---'
rg -n -C4 --type=ts 'zohomail|authType: .managed.|webhookSecret|keyBuilder|source === .webhook.' packages/corsair packages/zohomail --glob '*test.ts' --glob '*.ts' | head -n 500
printf '%s\n' '--- webhook dispatch and keyBuilder call sites ---'
rg -n -C6 --type=ts 'keyBuilder\\(|source: .webhook.|source === .webhook|build.*Key|plugin\\.keyBuilder' packages/corsair/core packages/corsair/hub
printf '%s\n' '--- managed delivery paths ---'
rg -n -C6 --type=ts 'processManagedOAuthDelivery|managed.*webhook|webhook.*managed|deliveryUrl|authType === .managed.' packages/corsair

Repository: corsairdev/corsair

Length of output: 31346


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- keyBuilder call sites ---'
rg -n -C5 --type=ts 'keyBuilder|source.*webhook|source.*endpoint' packages/corsair/core packages/corsair/hub | head -n 600
printf '%s\n' '--- webhook request processing ---'
rg -n -C5 --type=ts 'pluginWebhookMatcher|pluginTenantWebhookMatcher|webhook.*key|key.*webhook|processWebhook|dispatchWebhook' packages/corsair/core packages/corsair/hub packages/corsair | head -n 800
printf '%s\n' '--- managed hub delivery implementation ---'
sed -n '1,230p' packages/corsair/hub/managed-oauth.ts

Repository: corsairdev/corsair

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

index = Path("packages/zohomail/index.ts").read_text()
challenge = Path("packages/zohomail/webhooks/challenge.ts").read_text()
auth_types = Path("packages/corsair/core/auth/types.ts").read_text()
bind = Path("packages/corsair/core/webhooks/bind.ts").read_text()

managed = re.search(r"managed:\s*\{\s*integration:\s*\[\],\s*account:\s*\[(.*?)\]\s*as const", auth_types, re.S)
assert managed, "managed auth fields not found"
managed_fields = managed.group(1)
assert "webhook_signature" in managed_fields

assert "await ctx.keys.set_webhook_signature(hookSecret)" in challenge
assert re.search(
    r"if \(source === 'webhook'\) \{\s*"
    r"if \(ctx\.authType === 'managed'\) \{\s*"
    r"throw new Error\(\s*"
    r"\'[^\']*webhook signature is not available in managed mode",
    index,
    re.S,
)
assert "keyBuilder(ctx, 'webhook')" in bind

print("managed account fields include webhook_signature: yes")
print("Zoho handshake persists x-hook-secret through set_webhook_signature: yes")
print("webhook binding invokes keyBuilder with source='webhook': yes")
print("Zoho keyBuilder throws for authType='managed' before get_webhook_signature: yes")
print("Conclusion: a managed handshake can store webhook_signature, but subsequent bound webhook handling rejects managed auth before reading it.")
PY

Repository: corsairdev/corsair

Length of output: 276


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path

index = Path("packages/zohomail/index.ts").read_text()
challenge = Path("packages/zohomail/webhooks/challenge.ts").read_text()
auth_types = Path("packages/corsair/core/auth/types.ts").read_text()
bind = Path("packages/corsair/core/webhooks/bind.ts").read_text()

start = auth_types.index("\tmanaged:")
end = auth_types.index("\n\t},", start)
managed_block = auth_types[start:end]
assert "webhook_signature" in managed_block, managed_block

assert "await ctx.keys.set_webhook_signature(hookSecret)" in challenge
assert "if (ctx.authType === 'managed')" in index
guard_start = index.index("if (ctx.authType === 'managed')")
guard_end = index.index("\n\t\t\t\t}", guard_start) + len("\n\t\t\t\t}")
guard = index[guard_start:guard_end]
assert "webhook signature is not available in managed mode" in guard
assert "keyBuilder(ctx, 'webhook')" in bind

print("managed auth block:")
print(managed_block)
print("managed account fields include webhook_signature: yes")
print("Zoho handshake persists x-hook-secret through set_webhook_signature: yes")
print("webhook binding invokes keyBuilder with source='webhook': yes")
print("Zoho keyBuilder throws for authType='managed' before get_webhook_signature: yes")
print("Conclusion: the guard rejects a managed webhook after the handshake stores the signature.")
PY

Repository: corsairdev/corsair

Length of output: 694


Allow managed connections to resolve webhook_signature. The managed account fields include webhook_signature, and the handshake persists x-hook-secret through set_webhook_signature. Webhook binding calls keyBuilder before the handler, so this guard rejects the handshake and all later managed deliveries. Remove the guard or bypass key resolution for the handshake.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zohomail/index.ts` around lines 346 - 350, Update the managed branch
in the webhook authentication flow to allow managed connections to resolve
webhook_signature, removing or bypassing the error thrown for ctx.authType ===
'managed'. Preserve webhook key resolution for the handshake and subsequent
managed deliveries, including the persisted x-hook-secret from
set_webhook_signature.

Arm the provider subscription at managed connect + renewal, and verify
inbound events via the stored clientState — outlook, onedrive, teams,
sharepoint. Reuses the #469 BYO engine; managed just resolves the token
from the Hub.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/dropbox/index.ts (1)

390-408: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the managed key-generation block into a shared helper. Both files re-implement the same sequence: check ctx.hub, build managedContext, call getManagedAccessToken, call attachManagedRefreshAuth, and return the access token. Move this into a shared helper in corsair/hub (for example resolveManagedAccessToken(ctx, plugin)) that plugins call with their plugin name and any plugin-specific pre-checks (such as GitLab's host restriction) kept local.

  • packages/dropbox/index.ts#L390-L408: replace the inline hub-check/managedContext/getManagedAccessToken/attachManagedRefreshAuth sequence with a call to the shared helper, passing 'dropbox' as the plugin name.
  • packages/gitlab/index.ts#L927-L952: keep the gitlab.com host restriction as a local pre-check, then replace the remaining hub-check/managedContext/getManagedAccessToken/attachManagedRefreshAuth sequence with a call to the same shared helper, passing 'gitlab' as the plugin name.
♻️ Proposed shared helper sketch
// packages/corsair/hub/managed-auth.ts
export async function resolveManagedAccessToken(
	ctx: { hub?: unknown; keys: unknown; tenantId?: string } & Record<string, unknown>,
	plugin: string,
): Promise<string> {
	if (!ctx.hub) {
		throw new Error(
			`[auth-missing:${plugin}:managed]: Hub config is required for managed auth. Pass hub: { ... } to createCorsair().`,
		);
	}

	const managedContext = {
		keys: ctx.keys,
		hub: ctx.hub,
		plugin,
		tenantId: ctx.tenantId,
	};

	const result = await getManagedAccessToken(managedContext);
	await attachManagedRefreshAuth(ctx, managedContext);
	return result.accessToken;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dropbox/index.ts` around lines 390 - 408, Extract the shared
managed-auth sequence into a helper such as resolveManagedAccessToken in
packages/corsair/hub, preserving the hub validation, managed context
construction, token retrieval, refresh attachment, and access-token return. In
packages/dropbox/index.ts lines 390-408, replace the inline sequence with the
helper using 'dropbox'; in packages/gitlab/index.ts lines 927-952, retain the
gitlab.com host pre-check locally and replace the remaining sequence with the
helper using 'gitlab'.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/dropbox/index.ts`:
- Around line 390-408: Extract the shared managed-auth sequence into a helper
such as resolveManagedAccessToken in packages/corsair/hub, preserving the hub
validation, managed context construction, token retrieval, refresh attachment,
and access-token return. In packages/dropbox/index.ts lines 390-408, replace the
inline sequence with the helper using 'dropbox'; in packages/gitlab/index.ts
lines 927-952, retain the gitlab.com host pre-check locally and replace the
remaining sequence with the helper using 'gitlab'.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b53dbd96-24b5-4255-bc1a-5d3feb1828e6

📥 Commits

Reviewing files that changed from the base of the PR and between 20ef459 and a973a31.

📒 Files selected for processing (36)
  • packages/asana/index.ts
  • packages/asana/jest.config.cjs
  • packages/corsair/hub/managed-oauth.ts
  • packages/corsair/oauth/renewal.ts
  • packages/corsair/tests/managed-oauth-report.test.ts
  • packages/corsair/tests/subscription-renewal.test.ts
  • packages/dropbox/index.ts
  • packages/dropbox/jest.config.cjs
  • packages/gitlab/index.ts
  • packages/gitlab/jest.config.cjs
  • packages/hubspot/index.ts
  • packages/hubspot/jest.config.cjs
  • packages/linear/index.ts
  • packages/linear/jest.config.cjs
  • packages/notion/index.ts
  • packages/notion/jest.config.cjs
  • packages/onedrive/index.ts
  • packages/onedrive/jest.config.cjs
  • packages/onedrive/managed-webhook.test.ts
  • packages/outlook/index.ts
  • packages/outlook/jest.config.cjs
  • packages/outlook/managed-webhook.test.ts
  • packages/sharepoint/index.ts
  • packages/sharepoint/jest.config.cjs
  • packages/sharepoint/managed-webhook.test.ts
  • packages/sharepoint/webhooks/list-changes.ts
  • packages/sharepoint/webhooks/types.ts
  • packages/slack/index.ts
  • packages/slack/jest.config.cjs
  • packages/spotify/index.ts
  • packages/spotify/jest.config.cjs
  • packages/teams/index.ts
  • packages/teams/jest.config.cjs
  • packages/teams/managed-webhook.test.ts
  • packages/zohomail/index.ts
  • packages/zohomail/jest.config.cjs
🚧 Files skipped from review as they are similar to previous changes (25)
  • packages/teams/jest.config.cjs
  • packages/sharepoint/managed-webhook.test.ts
  • packages/spotify/index.ts
  • packages/notion/index.ts
  • packages/teams/managed-webhook.test.ts
  • packages/corsair/oauth/renewal.ts
  • packages/sharepoint/index.ts
  • packages/linear/index.ts
  • packages/sharepoint/webhooks/list-changes.ts
  • packages/outlook/managed-webhook.test.ts
  • packages/asana/index.ts
  • packages/corsair/hub/managed-oauth.ts
  • packages/sharepoint/jest.config.cjs
  • packages/hubspot/index.ts
  • packages/onedrive/jest.config.cjs
  • packages/sharepoint/webhooks/types.ts
  • packages/slack/index.ts
  • packages/outlook/jest.config.cjs
  • packages/teams/index.ts
  • packages/outlook/index.ts
  • packages/corsair/tests/subscription-renewal.test.ts
  • packages/onedrive/managed-webhook.test.ts
  • packages/corsair/tests/managed-oauth-report.test.ts
  • packages/onedrive/index.ts
  • packages/zohomail/index.ts

@yuvrxj-afk

Copy link
Copy Markdown
Collaborator Author

@greptileai

}

const clientState = ctx.options?.webhookClientState;
const clientState = ctx.key;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Batched notifications bypass clientState verification

When SharePoint sends multiple notifications in one request, this call accepts the entire batch after the verifier checks only value[0].clientState, so notifications later in the batch with missing or mismatched client state reach the handler as authenticated events.

How this was verified: verifySharepointWebhookSignature compares only the first notification against ctx.key and does not validate the remaining entries.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Hey @yuvrxj-afk, thanks for the contribution! 🏴‍☠️ Before a maintainer reviews, please fix the items below — the review re-runs automatically on your next push.

Must fix

  • P1 packages/sharepoint/webhooks/list-changes.ts:31Batched notifications bypass clientState verification
    When SharePoint sends multiple notifications in one request, this call accepts the entire batch after the verifier checks only value[0].clientState, so notifications later in the batch with missing or mismatched client state reach the handler as authenticated events.

How this was verified: verifySharepointWebhookSignature compares only the first notification against ctx.key and does not validate the remaining entries.

PR requirements (rules)

  • R1 — This PR touches: asana, dropbox, gitlab, hubspot, linear, notion, onedrive, outlook, sharepoint, slack, spotify, teams, zohomail
  • R3 — Description section is empty or placeholder
  • R4 — Required in "Screenshots / Demos" before a maintainer reviews

If anything remains after your next push, a bot commit will clean it up; a maintainer always does the final review and merge.

@github-actions github-actions Bot added the bot:round-1 Review bot posted consolidated findings label Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:round-1 Review bot posted consolidated findings core Changes in packages/corsair docs Docs / Mintlify / markdown changes gate:failed Plugin PR gate checks failing plugin Changes inside a plugin package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants