Skip to content

Stage#9660

Merged
evereq merged 1 commit intostagefrom
develop
Apr 16, 2026
Merged

Stage#9660
evereq merged 1 commit intostagefrom
develop

Conversation

@evereq
Copy link
Copy Markdown
Member

@evereq evereq commented Apr 16, 2026

fix: Enhance Zapier CLI app

PR

Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.


Note

Medium Risk
Touches authentication/token verification paths and changes how public endpoints resolve integrations, so misconfiguration of JWT_SECRET or tenant/org resolution could break Zapier connections. Dependency bumps and Zapier app OAuth endpoint changes may also affect existing installs/refresh behavior.

Overview
Improves the Zapier integration to work with multi-app OAuth by accepting either legacy opaque tokens or signed JWT access tokens across Zapier’s auth/test, auth/me, and webhook subscription endpoints.

This introduces centralized bearer-token resolution in ZapierService (JWT verification via environment.JWT_SECRET, tenant/org-based integration lookup with a fail-closed ambiguity check) and adjusts token setting lookups to bypass tenant scoping for @Public() endpoints.

Separately updates the Zapier CLI app to use the /api/integration/ever-gauzy/oauth/* authorization/token endpoints, disables auto-refresh, adds a global cleanInputData flag, and bumps Zapier platform dependencies to 18.4.0 (plus skipLibCheck for the Zapier app TS build).

Reviewed by Cursor Bugbot for commit eda1980. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Add JWT-based auth to the Zapier integration and switch the CLI app to the multi-app OAuth flow, while keeping legacy opaque tokens working. This improves connection reliability and unifies token handling across auth tests and webhooks.

  • New Features

    • Resolve Bearer tokens by trying opaque tokens first, then JWT verification; used by webhooks, auth test, and connection label.
    • Switch Zapier CLI app to /api/integration/ever-gauzy/oauth/{authorize,token} and disable refresh (use long-lived JWTs).
    • Improve public token lookup to bypass tenant scoping and add tenant/organization-aware integration resolution with clear errors.
    • Update zapier-platform-core and zapier-platform-cli to 18.4.0; add skipLibCheck and set app flag cleanInputData: false.
  • Migration

    • Redeploy the Zapier CLI app with the new OAuth endpoints; existing Zaps keep working via backward-compatible tokens.
    • Ensure JWT_SECRET is set for the API so JWTs can be verified.
    • For tenants with multiple Zapier integrations, reconnect using an org-scoped token to avoid ambiguity.

Written for commit eda1980. Summary will update on new commits.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: ASSERTIVE

Plan: Pro

Run ID: 8538a33d-9580-48a2-9a4d-86c6b7af3bfb

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 develop

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 and usage tips.

@evereq evereq merged commit 86b7697 into stage Apr 16, 2026
34 of 44 checks passed
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eda198064f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* JWTs. Configure the Zapier OAuth client's accessTokenTtl for a
* long TTL (e.g. 365 days) to avoid frequent reconnections.
*/
autoRefresh: 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.

P1 Badge Keep token refresh enabled for expiring OAuth JWTs

Setting autoRefresh to false here removes Zapier’s ability to renew access tokens, but the new /api/integration/ever-gauzy/oauth/token exchange does not return a refresh_token (see OAuthAppController.token) and access tokens are still bounded by global JWT_TOKEN_EXPIRATION_TIME in AuthService.exchangeOAuthAppAuthorizationCode (default is 1 day). As a result, established Zap connections will expire and require manual reconnection instead of self-recovering, which is a production regression from the previous refresh-capable flow.

Useful? React with 👍 / 👎.

integrationId: integration.id,
name: IntegrationEnum.ZAPIER
};
} catch {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Only swallow NotFound when bearer integration resolution fails

This empty catch swallows every failure from resolveIntegrationFromBearerToken, not just the intended “integration not created yet” case. That means errors like BadRequestException from tenant/org ambiguity (multiple Zapier integrations) are masked, and the method falls back to verifyJwtToken, so /auth/test can report success while webhook subscription endpoints later fail on the same token resolution path. Restrict the fallback to explicit not-found conditions so auth test and runtime behavior stay consistent.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit eda1980. Configure here.

};
} catch {
// No IntegrationTenant yet — fall through to JWT-only verification
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Catch-all silently swallows configuration errors from resolution

Medium Severity

The bare catch blocks around resolveIntegrationFromBearerToken in testAuth and getConnectionInfo swallow all exceptions — including BadRequestException ("Multiple Zapier integrations found…") and infrastructure errors — then fall through to JWT-only verification. Because verifyJwtToken can succeed independently, these endpoints return authenticated: true even when the integration is misconfigured (ambiguous multi-org setup) or the database is unreachable. The resolveIntegrationFromBearerToken method itself discriminates errors carefully, but the controller's unfiltered catch undoes that. Only NotFoundException and UnauthorizedException are appropriate to catch here; other errors like BadRequestException indicate real configuration problems that the user needs to see.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eda1980. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR enhances the Zapier integration by adding JWT-based bearer token resolution alongside the existing opaque token path, switches the Zapier CLI app to the multi-app OAuth endpoints (/api/integration/ever-gauzy/oauth/*), disables auto-refresh in favour of long-lived JWTs, and bumps zapier-platform-core/zapier-platform-cli to 18.4.0.

Two P1 issues need attention before merge:

  • deleteWebhook catches all exceptions (including 401/403) and converts them to InternalServerErrorException, unlike the correctly-implemented createWebhook.
  • testAuth and getConnectionInfo use a bare catch {} that silently swallows infrastructure errors from resolveIntegrationFromBearerToken, potentially allowing JWT authentication to succeed even when the DB is unavailable.

Confidence Score: 3/5

Not safe to merge as-is; two P1 bugs in error handling paths affect HTTP status codes and can mask DB failures during auth.

Two P1 issues were found: deleteWebhook incorrectly converts auth errors to 500 (incorrect status codes returned to Zapier platform), and bare catch{} blocks in testAuth/getConnectionInfo can silently mask DB failures and return authenticated:true incorrectly. Both are on the changed code paths and require fixes before merge.

zapier-webhook.controller.ts (deleteWebhook error handling) and zapier-authorization.controller.ts (bare catch blocks in testAuth and getConnectionInfo)

Important Files Changed

Filename Overview
packages/plugins/integration-zapier/src/lib/zapier-webhook.controller.ts deleteWebhook catch block converts all exceptions (including 401/403) to InternalServerErrorException, unlike the correctly-implemented createWebhook; P1 bug
packages/plugins/integration-zapier/src/lib/zapier-authorization.controller.ts New auth/test and auth/me endpoints; bare catch{} in both handlers silently swallows infrastructure errors (DB failures) and falls through to JWT verification
packages/plugins/integration-zapier/src/lib/zapier.service.ts Core service adding JWT + opaque token resolution; has dead code in resolveIntegrationFromBearerToken catch block and a misleading error on missing JWT secret
packages/plugins/integration-zapier/zapier/src/authentication.ts Switches to /api/integration/ever-gauzy/oauth/{authorize,token} endpoints and disables autoRefresh; auth/test and auth/me URLs are correct
packages/plugins/integration-zapier/zapier/src/index.ts Adds cleanInputData: false global flag; straightforward, no issues
packages/plugins/integration-zapier/zapier/package.json Bumps zapier-platform-core and zapier-platform-cli to 18.4.0; straightforward dependency update
packages/plugins/integration-zapier/zapier/tsconfig.json Adds skipLibCheck: true to bypass zapier-platform-core type conflicts; intentional and safe

Sequence Diagram

sequenceDiagram
    participant Z as Zapier CLI App
    participant EG as multi-app OAuth provider
    participant ZA as /zapier/auth/test and /zapier/auth/me
    participant WH as /zapier/webhooks
    participant SVC as ZapierService
    participant DB as Database

    Note over Z,EG: OAuth flow - new endpoints
    Z->>EG: GET /authorize with state param
    EG-->>Z: Redirect with authorization code
    Z->>EG: POST /token with code and credentials
    EG-->>Z: JWT access_token (long-lived, autoRefresh disabled)

    Note over Z,ZA: Auth test - supports opaque and JWT
    Z->>ZA: GET /auth/test with Bearer token
    ZA->>SVC: resolveIntegrationFromBearerToken(token)
    SVC->>DB: findOne by opaque token value
    alt opaque token found
        DB-->>SVC: IntegrationTenant
        SVC-->>ZA: integration
        ZA-->>Z: authenticated true with integrationId
    else NotFoundException - try JWT path
        SVC->>SVC: verifyJwtToken(token)
        SVC->>DB: findIntegrationByTenantId
        DB-->>SVC: IntegrationTenant
        ZA-->>Z: authenticated true
    else DB failure - bare catch swallows error
        ZA->>SVC: verifyJwtToken(token)
        ZA-->>Z: authenticated true - false positive risk
    end

    Note over Z,WH: Webhook subscribe
    Z->>WH: POST /webhooks with Bearer token
    WH->>SVC: resolveIntegrationFromBearerToken(token)
    SVC-->>WH: integration
    WH-->>Z: 201 subscription created

    Note over Z,WH: Webhook unsubscribe - bug
    Z->>WH: DELETE /webhooks/:id with Bearer token
    WH->>SVC: resolveIntegrationFromBearerToken(token)
    SVC-->>WH: throws NotFoundException or UnauthorizedException
    WH-->>Z: 500 InternalServerError - should be 401 or 403
Loading

Comments Outside Diff (1)

  1. packages/plugins/integration-zapier/src/lib/zapier-webhook.controller.ts, line 244-249 (link)

    P1 deleteWebhook wraps all exceptions as 500

    Every exception thrown inside the try block — including ForbiddenException (403), BadRequestException (400), and UnauthorizedException (401) from resolveIntegrationFromBearerToken — is caught and unconditionally rethrown as InternalServerErrorException. This differs from createWebhook, which correctly re-throws known HTTP exception types. Callers (including Zapier's platform) will always see a 500 for auth failures and invalid inputs, making it impossible to distinguish "bad token" from "server crash".

Reviews (1): Last reviewed commit: "Merge pull request #9655 from ever-co/fi..." | Re-trigger Greptile

Comment on lines +249 to +258
// Try opaque token first (backward compat) — resolves via IntegrationTenant
try {
const integration = await this.zapierService.resolveIntegrationFromBearerToken(token);
return {
authenticated: true,
integrationId: integration.id,
name: IntegrationEnum.ZAPIER
};
} catch {
// No IntegrationTenant yet — fall through to JWT-only verification
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 Bare catch {} silently swallows infrastructure errors

Both testAuth (lines 249–258) and getConnectionInfo (lines 296–305) use a bare catch {} that swallows ALL exceptions from resolveIntegrationFromBearerToken, including database failures. resolveIntegrationFromBearerToken already re-throws non-NotFoundException infrastructure errors immediately, but this inner catch {} discards them and falls through to JWT verification. On a DB failure with a valid JWT token, the endpoint returns authenticated: true even though the opaque-token path could not be checked — silent false positive.

The catch should be narrowed to only the "not found" case:

} catch (e) {
    if (!(e instanceof NotFoundException)) {
        throw e; // Re-throw infrastructure errors
    }
    // NotFoundException — no opaque-token match, fall through to JWT
}

The same fix is needed in getConnectionInfo at the equivalent block.

Comment on lines +917 to +925
} catch (error: any) {
// Let known HTTP exceptions (auth, not-found, bad-request) surface with their status code.
// Only re-throw unexpected errors (DB failures, etc.) as-is.
if (error instanceof HttpException) {
this.logger.debug('resolveIntegrationFromBearerToken: no integration found — %s', error?.message);
throw error;
}
throw error;
}
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.

P2 Dead code — both catch branches are identical

The if/else in the JWT catch block logs differently for HttpException but both branches unconditionally throw error. The comment implies they should behave differently for "infrastructure errors", but the code is identical for both paths. This is misleading; the if can be removed entirely.

Suggested change
} catch (error: any) {
// Let known HTTP exceptions (auth, not-found, bad-request) surface with their status code.
// Only re-throw unexpected errors (DB failures, etc.) as-is.
if (error instanceof HttpException) {
this.logger.debug('resolveIntegrationFromBearerToken: no integration found — %s', error?.message);
throw error;
}
throw error;
}
} catch (error: any) {
this.logger.debug('resolveIntegrationFromBearerToken: no integration found — %s', error?.message);
throw error;
}

Comment on lines +935 to +944
verifyJwtToken(token: string): { id: string; tenantId: string; organizationId?: string } {
try {
const decoded = verify(token, environment.JWT_SECRET!);
if (typeof decoded !== 'object' || !decoded || !('tenantId' in decoded)) {
throw new Error('Invalid JWT payload structure');
}
return decoded as { id: string; tenantId: string; organizationId?: string };
} catch {
throw new UnauthorizedException('Invalid or expired access token');
}
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.

P2 Misconfigured secret produces a misleading 401

The non-null assertion on environment.JWT_SECRET suppresses the TypeScript error, but if the secret is unset at runtime, jsonwebtoken throws internally and the bare catch {} re-throws it as UnauthorizedException('Invalid or expired access token'), making a server misconfiguration indistinguishable from a bad token in both HTTP responses and logs. Consider guarding for the missing secret before calling verify and surfacing it as InternalServerErrorException instead.

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.

2 participants