|
1 | 1 | import crypto from 'crypto' |
2 | | -import { db, webhook } from '@sim/db' |
| 2 | +import { db, webhook, workflow } from '@sim/db' |
3 | 3 | import { createLogger } from '@sim/logger' |
4 | 4 | import { and, eq } from 'drizzle-orm' |
5 | 5 | import type { NextRequest } from 'next/server' |
6 | 6 | import { NextResponse } from 'next/server' |
7 | 7 | import { safeCompare } from '@/lib/core/security/encryption' |
| 8 | +import { resolveEnvVarsInObject } from '@/lib/webhooks/env-resolver' |
8 | 9 | import type { |
9 | 10 | AuthContext, |
10 | 11 | EventMatchContext, |
@@ -47,6 +48,46 @@ export function validateZoomSignature( |
47 | 48 | } |
48 | 49 | } |
49 | 50 |
|
| 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 | + |
50 | 91 | export const zoomHandler: WebhookProviderHandler = { |
51 | 92 | verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { |
52 | 93 | const secretToken = providerConfig.secretToken as string | undefined |
@@ -166,22 +207,16 @@ export const zoomHandler: WebhookProviderHandler = { |
166 | 207 | const bodyForSignature = |
167 | 208 | rawBody !== undefined && rawBody !== null ? rawBody : JSON.stringify(body) |
168 | 209 |
|
169 | | - let rows: { providerConfig: unknown }[] = [] |
| 210 | + let rows: Array<{ secretToken: string }> = [] |
170 | 211 | 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) |
177 | 213 | } catch (err) { |
178 | 214 | logger.warn(`[${requestId}] Failed to look up webhook secret for Zoom validation`, err) |
179 | 215 | return null |
180 | 216 | } |
181 | 217 |
|
182 | 218 | 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 |
185 | 220 | if ( |
186 | 221 | secretToken && |
187 | 222 | validateZoomSignature(secretToken, signature, timestamp, bodyForSignature) |
|
0 commit comments