Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions apps/sim/app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
shouldRecreateExternalWebhookSubscription,
} from '@/lib/webhooks/provider-subscriptions'
import { getProviderHandler } from '@/lib/webhooks/providers'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import { mergeNonUserFields } from '@/lib/webhooks/utils'
import { findConflictingWebhookPathOwner } from '@/lib/webhooks/utils.server'
import { listAccessibleWorkspaceRowsForUser } from '@/lib/workspaces/utils'
Expand Down Expand Up @@ -573,13 +574,20 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
}
}
} catch (err) {
logger.error(`[${requestId}] Error creating external webhook subscription`, err)
const isConfigurationError = err instanceof WebhookDeploymentConfigurationError
if (isConfigurationError) {
logger.warn(`[${requestId}] Rejected webhook trigger configuration`, {
error: err.message,
})
} else {
logger.error(`[${requestId}] Error creating external webhook subscription`, err)
}
return NextResponse.json(
{
error: 'Failed to create external webhook subscription',
details: getErrorMessage(err, 'Unknown error'),
},
{ status: 500 }
{ status: isConfigurationError ? 400 : 500 }
)
}
} else {
Expand Down
15 changes: 9 additions & 6 deletions apps/sim/lib/webhooks/providers/airtable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { db } from '@sim/db'
import { account, webhook } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { toStringOrNull } from '@sim/utils/coerce'
import { toRecord } from '@sim/utils/object'
import { eq } from 'drizzle-orm'
import { validateAirtableId } from '@/lib/core/security/input-validation'
Expand All @@ -14,6 +15,7 @@ import {
getNotificationUrl,
getProviderConfig,
} from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
DeleteSubscriptionContext,
FormatInputContext,
Expand Down Expand Up @@ -435,30 +437,30 @@ export const airtableHandler: WebhookProviderHandler = {
}: SubscriptionContext): Promise<SubscriptionResult | undefined> {
try {
const config = getProviderConfig(webhookRecord)
const { baseId, tableId, includeCellValuesInFieldIds, credentialId } = config as {
baseId?: string
tableId?: string
const { includeCellValuesInFieldIds, credentialId } = config as {
includeCellValuesInFieldIds?: string
credentialId?: string
}
const baseId = toStringOrNull(config.baseId)
const tableId = toStringOrNull(config.tableId)

if (!baseId || !tableId) {
logger.warn(`[${requestId}] Missing baseId or tableId for Airtable webhook creation.`, {
webhookId: webhookRecord.id,
})
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Base ID and Table ID are required to create Airtable webhook. Please provide valid Airtable base and table IDs.'
)
}

const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId')
if (!baseIdValidation.isValid) {
throw new Error(baseIdValidation.error)
throw new WebhookDeploymentConfigurationError(baseIdValidation.error)
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
}

const tableIdValidation = validateAirtableId(tableId, 'tbl', 'tableId')
if (!tableIdValidation.isValid) {
throw new Error(tableIdValidation.error)
throw new WebhookDeploymentConfigurationError(tableIdValidation.error)
}

const accessToken = credentialId
Expand Down Expand Up @@ -535,6 +537,7 @@ export const airtableHandler: WebhookProviderHandler = {
)
return { providerConfigUpdates: { externalId: responseBody.id } }
} catch (error: unknown) {
if (error instanceof WebhookDeploymentConfigurationError) throw error
const err = error as Error
logger.error(
`[${requestId}] Exception during Airtable webhook creation for webhook ${webhookRecord.id}.`,
Expand Down
8 changes: 6 additions & 2 deletions apps/sim/lib/webhooks/providers/ashby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { isRecordLike, omit, toRecord } from '@sim/utils/object'
import { NextResponse } from 'next/server'
import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
AuthContext,
DeleteSubscriptionContext,
Expand Down Expand Up @@ -235,13 +236,15 @@ export const ashbyHandler: WebhookProviderHandler = {
}

if (!apiKey) {
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Ashby API Key is required. Please provide your API Key with apiKeysWrite permission in the trigger configuration.'
)
}

if (!triggerId) {
throw new Error('Trigger ID is required to create Ashby webhook.')
throw new WebhookDeploymentConfigurationError(
'Trigger ID is required to create Ashby webhook.'
)
}

const { ASHBY_TRIGGER_ACTION_MAP } = await import('@/triggers/ashby/utils')
Expand Down Expand Up @@ -322,6 +325,7 @@ export const ashbyHandler: WebhookProviderHandler = {
)
return { providerConfigUpdates: { externalId, secretToken } }
} catch (error: unknown) {
if (error instanceof WebhookDeploymentConfigurationError) throw error
const err = error as Error
logger.error(
`[${ctx.requestId}] Exception during Ashby webhook creation for webhook ${ctx.webhook.id}.`,
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/lib/webhooks/providers/attio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getNotificationUrl,
getProviderConfig,
} from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
AuthContext,
DeleteSubscriptionContext,
Expand Down Expand Up @@ -116,7 +117,7 @@ export const attioHandler: WebhookProviderHandler = {
logger.warn(`[${requestId}] Missing credentialId for Attio webhook creation.`, {
webhookId: webhookRecord.id,
})
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.'
)
}
Expand Down Expand Up @@ -222,6 +223,7 @@ export const attioHandler: WebhookProviderHandler = {

return { providerConfigUpdates: { externalId: webhookId, webhookSecret: secret || '' } }
} catch (error: unknown) {
if (error instanceof WebhookDeploymentConfigurationError) throw error
const message = toError(error).message
logger.error(
`[${requestId}] Exception during Attio webhook creation for webhook ${webhookRecord.id}.`,
Expand Down
7 changes: 5 additions & 2 deletions apps/sim/lib/webhooks/providers/bitbucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getNotificationUrl,
getProviderConfig,
} from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
DeleteSubscriptionContext,
EventMatchContext,
Expand Down Expand Up @@ -89,7 +90,7 @@ async function resolveBitbucketAccessToken(
requestId: string
): Promise<string> {
if (!credentialId) {
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Bitbucket account connection required. Connect or reconnect Bitbucket in the trigger configuration and try again.'
)
}
Expand All @@ -112,7 +113,9 @@ function readRequiredConfigString(
): string {
const value = config[key]
if (typeof value !== 'string' || !value.trim()) {
throw new Error(`Bitbucket ${label} is required to manage the repository webhook.`)
throw new WebhookDeploymentConfigurationError(
`Bitbucket ${label} is required to manage the repository webhook.`
)
}
return value.trim()
}
Expand Down
10 changes: 7 additions & 3 deletions apps/sim/lib/webhooks/providers/calendly.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
DeleteSubscriptionContext,
FormatInputContext,
Expand Down Expand Up @@ -37,7 +38,7 @@ export const calendlyHandler: WebhookProviderHandler = {
logger.warn(`[${ctx.requestId}] Missing apiKey for Calendly webhook creation.`, {
webhookId: ctx.webhook.id,
})
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Personal Access Token is required to create Calendly webhook. Please provide your Calendly Personal Access Token.'
)
}
Expand All @@ -46,7 +47,7 @@ export const calendlyHandler: WebhookProviderHandler = {
logger.warn(`[${ctx.requestId}] Missing organization URI for Calendly webhook creation.`, {
webhookId: ctx.webhook.id,
})
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Organization URI is required to create Calendly webhook. Please provide your Organization URI from the "Get Current User" operation.'
)
}
Expand All @@ -55,7 +56,9 @@ export const calendlyHandler: WebhookProviderHandler = {
logger.warn(`[${ctx.requestId}] Missing triggerId for Calendly webhook creation.`, {
webhookId: ctx.webhook.id,
})
throw new Error('Trigger ID is required to create Calendly webhook')
throw new WebhookDeploymentConfigurationError(
'Trigger ID is required to create Calendly webhook'
)
}

const notificationUrl = getNotificationUrl(ctx.webhook)
Expand Down Expand Up @@ -152,6 +155,7 @@ export const calendlyHandler: WebhookProviderHandler = {
)
return { providerConfigUpdates: { externalId: webhookId } }
} catch (error: unknown) {
if (error instanceof WebhookDeploymentConfigurationError) throw error
const err = error as Error
logger.error(
`[${ctx.requestId}] Exception during Calendly webhook creation for webhook ${ctx.webhook.id}.`,
Expand Down
5 changes: 4 additions & 1 deletion apps/sim/lib/webhooks/providers/checkr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { and, eq, isNull, ne } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
AuthContext,
DeleteSubscriptionContext,
Expand Down Expand Up @@ -198,7 +199,9 @@ export const checkrHandler: WebhookProviderHandler = {
const providerConfig = getProviderConfig(ctx.webhook)
const apiKey = typeof providerConfig.apiKey === 'string' ? providerConfig.apiKey.trim() : ''
if (!apiKey) {
throw new Error('Checkr API key is required to register the webhook.')
throw new WebhookDeploymentConfigurationError(
'Checkr API key is required to register the webhook.'
)
}

const notificationUrl = getNotificationUrl(ctx.webhook)
Expand Down
10 changes: 7 additions & 3 deletions apps/sim/lib/webhooks/providers/clickup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getNotificationUrl,
getProviderConfig,
} from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
DeleteSubscriptionContext,
EventMatchContext,
Expand Down Expand Up @@ -46,7 +47,7 @@ async function resolveClickUpAccessToken(
requestId: string
): Promise<string> {
if (!credentialId) {
throw new Error(
throw new WebhookDeploymentConfigurationError(
'ClickUp account connection required. Please connect your ClickUp account in the trigger configuration and try again.'
)
}
Expand All @@ -72,7 +73,9 @@ function parseOptionalNumericId(value: unknown, label: string): number | undefin
if (!raw) return undefined
const parsed = Number(raw)
if (!Number.isInteger(parsed)) {
throw new Error(`ClickUp ${label} must be a whole number. Received: ${raw}`)
throw new WebhookDeploymentConfigurationError(
`ClickUp ${label} must be a whole number. Received: ${raw}`
)
}
return parsed
}
Expand Down Expand Up @@ -154,7 +157,7 @@ export const clickupHandler: WebhookProviderHandler = {

const workspaceId = parseOptionalStringId(config.triggerWorkspaceId)
if (!workspaceId) {
throw new Error(
throw new WebhookDeploymentConfigurationError(
'ClickUp workspace is required. Please select a workspace in the trigger configuration and try again.'
)
}
Expand Down Expand Up @@ -271,6 +274,7 @@ export const clickupHandler: WebhookProviderHandler = {

return { providerConfigUpdates: { externalId, webhookSecret: secret } }
} catch (error: unknown) {
if (error instanceof WebhookDeploymentConfigurationError) throw error
const message = toError(error).message
logger.error(
`[${requestId}] Exception during ClickUp webhook creation for webhook ${webhookRecord.id}.`,
Expand Down
7 changes: 4 additions & 3 deletions apps/sim/lib/webhooks/providers/emailbison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
validateUrlWithDNS,
} from '@/lib/core/security/input-validation.server'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
DeleteSubscriptionContext,
EventMatchContext,
Expand Down Expand Up @@ -127,15 +128,15 @@ export const emailBisonHandler: WebhookProviderHandler = {
const triggerId = providerConfig.triggerId as string | undefined

if (!apiKey?.trim()) {
throw new Error('Email Bison API Key is required.')
throw new WebhookDeploymentConfigurationError('Email Bison API Key is required.')
}

if (!apiBaseUrl?.trim()) {
throw new Error('Email Bison Instance URL is required.')
throw new WebhookDeploymentConfigurationError('Email Bison Instance URL is required.')
}

if (!triggerId) {
throw new Error('Email Bison trigger ID is required.')
throw new WebhookDeploymentConfigurationError('Email Bison trigger ID is required.')
}

const { getEmailBisonEventTypeForTrigger } = await import('@/triggers/emailbison/utils')
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/webhooks/providers/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export class WebhookDeploymentConfigurationError extends Error {
constructor(message: string) {
constructor(message?: string) {
super(message)
this.name = 'WebhookDeploymentConfigurationError'
}
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/lib/webhooks/providers/fathom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createLogger } from '@sim/logger'
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
DeleteSubscriptionContext,
SubscriptionContext,
Expand All @@ -27,7 +28,7 @@ export const fathomHandler: WebhookProviderHandler = {
logger.warn(`[${requestId}] Missing apiKey for Fathom webhook creation.`, {
webhookId: webhook.id,
})
throw new Error(
throw new WebhookDeploymentConfigurationError(
'Fathom API Key is required. Please provide your API key in the trigger configuration.'
)
}
Expand Down Expand Up @@ -108,6 +109,7 @@ export const fathomHandler: WebhookProviderHandler = {

return { providerConfigUpdates: { externalId: responseBody.id } }
} catch (error: unknown) {
if (error instanceof WebhookDeploymentConfigurationError) throw error
const err = error as Error
logger.error(
`[${requestId}] Exception during Fathom webhook creation for webhook ${webhook.id}.`,
Expand Down
5 changes: 4 additions & 1 deletion apps/sim/lib/webhooks/providers/fivetran.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { generateRandomHex } from '@sim/utils/random'
import { NextResponse } from 'next/server'
import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
AuthContext,
DeleteSubscriptionContext,
Expand Down Expand Up @@ -228,7 +229,9 @@ export const fivetranHandler: WebhookProviderHandler = {
const providerConfig = getProviderConfig(ctx.webhook)
const credentials = readCredentials(providerConfig)
if (!credentials) {
throw new Error('A Fivetran API key and secret are required to register the webhook.')
throw new WebhookDeploymentConfigurationError(
'A Fivetran API key and secret are required to register the webhook.'
)
}

const triggerId = readString(providerConfig, 'triggerId')
Expand Down
16 changes: 12 additions & 4 deletions apps/sim/lib/webhooks/providers/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isRecordLike, toRecord } from '@sim/utils/object'
import { NextResponse } from 'next/server'
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
import type {
AuthContext,
DeleteSubscriptionContext,
Expand Down Expand Up @@ -182,15 +183,22 @@ export const gitlabHandler: WebhookProviderHandler = {
const triggerId = config.triggerId as string | undefined
const host = config.host as string | undefined

if (!accessToken)
throw new Error('GitLab Personal Access Token is required to create the webhook.')
if (!projectId) throw new Error('GitLab Project ID is required to create the webhook.')
if (!accessToken) {
throw new WebhookDeploymentConfigurationError(
'GitLab Personal Access Token is required to create the webhook.'
)
}
if (!projectId) {
throw new WebhookDeploymentConfigurationError(
'GitLab Project ID is required to create the webhook.'
)
}

try {
getGitLabApiBase(host)
} catch (error) {
if (error instanceof UnsafeGitLabHostError) {
throw new Error(
throw new WebhookDeploymentConfigurationError(
'GitLab host is invalid. Provide a domain like gitlab.example.com (no protocol, path, or credentials).'
)
}
Expand Down
Loading
Loading