Skip to content

Commit e000c5b

Browse files
committed
fix(zoom): resolve env-backed secrets during validation
Use the same env-aware secret resolution path for Zoom endpoint validation as regular delivery verification so URL validation works correctly when the secret token is stored via env references. Made-with: Cursor
1 parent 23ccc9b commit e000c5b

File tree

1 file changed

+45
-10
lines changed
  • apps/sim/lib/webhooks/providers

1 file changed

+45
-10
lines changed

apps/sim/lib/webhooks/providers/zoom.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import crypto from 'crypto'
2-
import { db, webhook } from '@sim/db'
2+
import { db, webhook, workflow } from '@sim/db'
33
import { createLogger } from '@sim/logger'
44
import { and, eq } from 'drizzle-orm'
55
import type { NextRequest } from 'next/server'
66
import { NextResponse } from 'next/server'
77
import { safeCompare } from '@/lib/core/security/encryption'
8+
import { resolveEnvVarsInObject } from '@/lib/webhooks/env-resolver'
89
import type {
910
AuthContext,
1011
EventMatchContext,
@@ -47,6 +48,46 @@ export function validateZoomSignature(
4748
}
4849
}
4950

51+
async function resolveZoomChallengeSecrets(
52+
path: string,
53+
requestId: string
54+
): Promise<Array<{ secretToken: string }>> {
55+
const rows = await db
56+
.select({
57+
providerConfig: webhook.providerConfig,
58+
userId: workflow.userId,
59+
workspaceId: workflow.workspaceId,
60+
})
61+
.from(webhook)
62+
.innerJoin(workflow, eq(webhook.workflowId, workflow.id))
63+
.where(and(eq(webhook.path, path), eq(webhook.provider, 'zoom'), eq(webhook.isActive, true)))
64+
65+
const resolvedRows = await Promise.all(
66+
rows.map(async (row) => {
67+
const rawConfig =
68+
row.providerConfig &&
69+
typeof row.providerConfig === 'object' &&
70+
!Array.isArray(row.providerConfig)
71+
? (row.providerConfig as Record<string, unknown>)
72+
: {}
73+
74+
try {
75+
const config = await resolveEnvVarsInObject(rawConfig, row.userId, row.workspaceId)
76+
const secretToken = typeof config.secretToken === 'string' ? config.secretToken : ''
77+
return { secretToken }
78+
} catch (error) {
79+
logger.warn(`[${requestId}] Failed to resolve Zoom webhook secret for challenge`, {
80+
error: error instanceof Error ? error.message : String(error),
81+
path,
82+
})
83+
return { secretToken: '' }
84+
}
85+
})
86+
)
87+
88+
return resolvedRows.filter((row) => row.secretToken)
89+
}
90+
5091
export const zoomHandler: WebhookProviderHandler = {
5192
verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) {
5293
const secretToken = providerConfig.secretToken as string | undefined
@@ -166,22 +207,16 @@ export const zoomHandler: WebhookProviderHandler = {
166207
const bodyForSignature =
167208
rawBody !== undefined && rawBody !== null ? rawBody : JSON.stringify(body)
168209

169-
let rows: { providerConfig: unknown }[] = []
210+
let rows: Array<{ secretToken: string }> = []
170211
try {
171-
rows = await db
172-
.select({ providerConfig: webhook.providerConfig })
173-
.from(webhook)
174-
.where(
175-
and(eq(webhook.path, path), eq(webhook.provider, 'zoom'), eq(webhook.isActive, true))
176-
)
212+
rows = await resolveZoomChallengeSecrets(path, requestId)
177213
} catch (err) {
178214
logger.warn(`[${requestId}] Failed to look up webhook secret for Zoom validation`, err)
179215
return null
180216
}
181217

182218
for (const row of rows) {
183-
const config = row.providerConfig as Record<string, unknown> | null
184-
const secretToken = (config?.secretToken as string) || ''
219+
const secretToken = row.secretToken
185220
if (
186221
secretToken &&
187222
validateZoomSignature(secretToken, signature, timestamp, bodyForSignature)

0 commit comments

Comments
 (0)