Skip to content

feat(router): add relayfile-cloud auth-aware Nango webhook routing#6

Draft
khaliqgant wants to merge 1 commit into
mainfrom
feat/router-auth-classification
Draft

feat(router): add relayfile-cloud auth-aware Nango webhook routing#6
khaliqgant wants to merge 1 commit into
mainfrom
feat/router-auth-classification

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

  • Ports the auth-classification logic from cloud PR #2472 into the agentrelay.com apex router
  • When WEBHOOK_ORIGIN flag = "relayfile-cloud", allowlisted non-lifecycle Nango ingest events (forward/sync/webhook from known providerConfigKeys) are forwarded to RELAYFILE_CLOUD_WORKER
  • Auth/lifecycle events (auth, connection.created) always fall through to cloud-web — handleAuthEvent is load-bearing and must stay in cloud
  • Dormant until flag is set — no production behaviour changes until WEBHOOK_ORIGIN is explicitly flipped to "relayfile-cloud" in ROUTER_CONFIG KV

Changes

  • router/wrangler.jsonc: add RELAYFILE_CLOUD_WORKER service binding + RELAYFILE_CLOUD_ORIGIN var
  • router/index.ts: add constants, auth-classification helpers, shouldUseNangoRelayfileCloudRoute, shouldUseRelayfileCloudWebhook, buildRelayfileCloudWebhookRequest, relayfile-cloud routing branch in fetch(); update shouldUseCloudWebWorker to exclude relayfile-cloud ingest path
  • router/test/webhook-routing.test.ts: 4 new tests (ingest routed to relayfile-cloud, lifecycle falls back to cloud-web, non-allowlisted provider falls back to cloud-web, loop-break header bypasses relayfile-cloud)
  • router/vitest.config.ts: new — scopes vitest to test/** so the suite runs standalone without the parent workspace config interfering

Parity with cloud #2472

Mirrors cloud/packages/router/index.ts from origin/worker/nango-auth-aware-router exactly:

  • Same RELAYFILE_CLOUD_NANGO_LIFECYCLE_TYPES, INGEST_TYPES, PROVIDER_CONFIG_KEYS sets
  • Same clone-to-peek / forward-original body strategy (HMAC-safe)
  • Same loop-break header (x-cloud-webhook-relayfile-cloud-forwarded: relayfile-cloud)
  • Same fallback to RELAYFILE_CLOUD_ORIGIN URL when service binding is absent

⚠️ DO NOT MERGE until

  • Lead reviews + dormant-verified
  • @khaliqgant is aware that merging this PR auto-deploys the live prod apex router (via deploy-router.yml trigger on router/** push to main)
  • Ideally after dev deployment is green

Test plan

  • npm --workspace router run test — 65 tests pass (8 new)
  • npm --workspace router run typecheck — clean

🤖 Generated with Claude Code


Summary by cubic

Adds auth-aware routing for Nango webhooks to relayfile-cloud in the apex router. Enabled only when WEBHOOK_ORIGIN=relayfile-cloud; allowlisted ingest events route to RELAYFILE_CLOUD_WORKER, while lifecycle/auth events continue to cloud-web.

  • New Features
    • Classifies Nango events and forwards only allowlisted ingest types (forward, sync, webhook) for known providerConfigKeys; lifecycle types (auth, connection.created) always go to cloud-web.
    • Adds loop-break header x-cloud-webhook-relayfile-cloud-forwarded and keeps HMAC-safe body handling (clone to inspect, forward original).
    • Adds RELAYFILE_CLOUD_WORKER service binding and RELAYFILE_CLOUD_ORIGIN fallback URL; excludes relayfile-cloud ingest from the cloud-web default path.
    • Adds 4 routing tests and a local vitest config scoped to router/test.

Written for commit 6f92571. Summary will update on new commits.

Review in cubic

Ports the auth-classification logic from cloud PR #2472 into the
agentrelay.com apex router. When the ROUTER_CONFIG `WEBHOOK_ORIGIN`
flag is set to "relayfile-cloud", allowlisted non-lifecycle Nango
ingest events (forward/sync/webhook for known providerConfigKeys) are
forwarded to the RELAYFILE_CLOUD_WORKER service binding; auth/lifecycle
events always fall through to cloud-web so handleAuthEvent keeps
running. Dormant until the flag is set.

Changes:
- Add RELAYFILE_CLOUD_WORKER binding + RELAYFILE_CLOUD_ORIGIN var to
  wrangler.jsonc
- Add WEBHOOK_ORIGIN_RELAYFILE_CLOUD, RELAYFILE_CLOUD_FORWARDED_*,
  lifecycle/ingest/provider-allowlist constants to index.ts
- Add isRecord, readString, readNangoWebhookType,
  readNangoProviderConfigKey, isRelayfileCloudNangoIngestRequest,
  isRelayfileCloudForward, shouldUseNangoRelayfileCloudRoute,
  shouldUseRelayfileCloudWebhook, buildRelayfileCloudWebhookRequest
- Update shouldUseCloudWebWorker to exclude relayfile-cloud ingest
- Add relayfile-cloud routing branch in fetch handler (mirrors the
  webhook-worker branch structure: service binding then origin URL)
- Add vitest.config.ts so the router test suite runs standalone
- Add 4 new tests covering: ingest routing, lifecycle fallback to
  cloud-web, non-allowlisted provider fallback, and loop-break header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ad4d34c0-efd4-4a61-b637-c426d4467042

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/router-auth-classification

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.

❤️ Share

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces routing support for forwarding specific Nango webhook ingest requests to relayfile-cloud (via either a service binding or a fallback origin) when the WEBHOOK_ORIGIN flag is configured. It includes parsing logic to identify allowlisted ingest requests while ensuring lifecycle events continue to route to cloud-web, along with corresponding unit tests and configuration updates. Feedback is provided regarding an optimization opportunity in router/index.ts to avoid redundant cloning and parsing of the request body by caching the parsed result and immediately returning false for non-POST requests.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread router/index.ts
Comment on lines +325 to +340
async function isRelayfileCloudNangoIngestRequest(request: Request): Promise<boolean> {
let body: unknown;
try {
body = await request.clone().json();
} catch {
return false;
}
if (!isRecord(body)) return false;
const type = readNangoWebhookType(body);
if (!type || RELAYFILE_CLOUD_NANGO_LIFECYCLE_TYPES.has(type)) return false;
if (!RELAYFILE_CLOUD_NANGO_INGEST_TYPES.has(type)) return false;
const providerConfigKey = readNangoProviderConfigKey(body);
return providerConfigKey
? RELAYFILE_CLOUD_NANGO_PROVIDER_CONFIG_KEYS.has(providerConfigKey)
: false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The isRelayfileCloudNangoIngestRequest function is called twice for every matching request (once during the shouldUseCloudWebWorker check and once in the main fetch routing logic). This causes the request body to be cloned and parsed as JSON twice, which introduces unnecessary CPU and memory overhead on the hot path.

Additionally, non-POST requests (e.g., GET or OPTIONS) sent to this path will still attempt to clone and parse the body, throwing an exception that is caught but still wastes resources.

We can optimize this by:

  1. Returning false immediately if the request method is not POST.
  2. Caching the parsed result in a WeakMap keyed by the Request object to avoid double-parsing.
const ingestRequestCache = new WeakMap<Request, Promise<boolean>>();

async function isRelayfileCloudNangoIngestRequest(request: Request): Promise<boolean> {
  if (request.method !== "POST") {
    return false;
  }

  let promise = ingestRequestCache.get(request);
  if (!promise) {
    promise = (async () => {
      let body: unknown;
      try {
        body = await request.clone().json();
      } catch {
        return false;
      }
      if (!isRecord(body)) return false;
      const type = readNangoWebhookType(body);
      if (!type || RELAYFILE_CLOUD_NANGO_LIFECYCLE_TYPES.has(type)) return false;
      if (!RELAYFILE_CLOUD_NANGO_INGEST_TYPES.has(type)) return false;
      const providerConfigKey = readNangoProviderConfigKey(body);
      return providerConfigKey
        ? RELAYFILE_CLOUD_NANGO_PROVIDER_CONFIG_KEYS.has(providerConfigKey)
        : false;
    })();
    ingestRequestCache.set(request, promise);
  }
  return promise;
}

@github-actions

Copy link
Copy Markdown
Contributor

Preview deployed!

Environment URL
Web https://efaa1502-agentrelay-web.agent-workforce.workers.dev

This is a Cloudflare Workers preview version of this PR's build.

@agent-relay-code

Copy link
Copy Markdown
Contributor

Both pass; working tree has only the untracked memory/ dir (not mine to touch, pre-existing). I made no code edits — the PR is mechanically and semantically clean per my review.

Let me write up the review.

Review: PR #6 — relayfile-cloud auth-aware Nango webhook routing

I read .workforce/pr.diff, .workforce/changed-files.txt, and .workforce/context.json, then traced the diff through router/index.ts, its callers, tests, and wrangler.jsonc. I ran the repo's canonical checks for the affected package (router): npm ci (clean), npm test65/65 pass, npm run typecheck (tsc --noEmit) → clean.

What the PR does

Adds a third Nango-webhook destination ("relayfile-cloud") to the Cloudflare router. When the WEBHOOK_ORIGIN ops flag is "relayfile-cloud", a POST to /api/v1/webhooks/nango is body-classified: allowlisted ingest events (type in {forward, sync, webhook} with an allowlisted providerConfigKey) are forwarded to the RELAYFILE_CLOUD_WORKER binding (or RELAYFILE_CLOUD_ORIGIN fallback); lifecycle events (auth, connection.created) and everything unrecognized stay on cloud-web. A forwarded-loop-break header mirrors the existing webhook-worker pattern.

Verification of correctness / safety

  • Fail-safe default preserved. isRelayfileCloudNangoIngestRequest returns false for unparseable bodies, missing/unknown type, lifecycle types, or non-allowlisted providers — so the conservative path (stay on cloud-web, run handleAuthEvent) is the default. No fail-open introduced.
  • Body stream not double-consumed. Every classifier reads request.clone().json(); the original request.body is only consumed by the final forward Request (duplex: "half"). The relayfile-cloud predicate runs twice (in shouldUseCloudWebWorker and again at index.ts:517), each on a fresh clone. The passing routing tests exercise this exact path, proving the forwarded body survives.
  • Loop break correct. Both x-cloud-webhook-worker-forwarded and the new x-cloud-webhook-relayfile-cloud-forwarded short-circuit the relayfile-cloud route (index.ts:359), and a dedicated test confirms it.
  • No external callers affected. shouldUseNangoRelayfileCloudRoute/shouldUseNangoWebhookWorkerRoute are used only in index.ts + tests; the literal "worker"WEBHOOK_ORIGIN_WORKER constant swap is value-preserving.
  • Lockfile change is benign. The package-lock.json diff (drops libc[], adds peer/dev flags on optional native esbuild/rollup/lightningcss binaries) is a mechanical regeneration; npm ci resolves cleanly and tests/typecheck pass, so no runtime dependency was dropped.

I made no edits — there were no lint/format/typo issues to mechanically fix, and the logic is sound, so nothing warranted an auto-edit.

Addressed comments

  • No bot or human review comments were available in .workforce/context.json, and the gh CLI is unavailable in this sandbox (and out of scope to use), so there were no review threads to reconcile against the current checkout.

Advisory Notes

  • Fall-through when both relayfile-cloud destinations are unset: if WEBHOOK_ORIGIN=relayfile-cloud and an allowlisted ingest arrives but neither RELAYFILE_CLOUD_WORKER nor RELAYFILE_CLOUD_ORIGIN is configured, the index.ts:517 block enters but returns from neither branch, falling through to the generic /cloud proxy (→ CLOUD_APP_ORIGIN). That is a reasonable, non-fail-open landing spot, and wrangler.jsonc always sets RELAYFILE_CLOUD_ORIGIN, so it's not reachable in prod. Worth a one-line code comment for future readers, but not a blocker and outside the mechanical-edit scope — leaving the code unchanged.
  • No CI test gate for router: the only PR workflow (preview.yml) builds/deploys web; router tests/typecheck aren't run in CI on PRs. Not introduced by this PR, but the new routing logic is only protected by local npm test. Flagging for human consideration; no change made here.

The change is well-scoped, tested, and verified green locally. The remaining items are advisory judgment calls for a human, and I cannot confirm the actual GitHub CI/merge status from this sandbox, so I am not declaring it merge-ready.

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.

1 participant