|
1 | 1 | import { createLogger } from '@sim/logger' |
2 | 2 | import type { NextRequest } from 'next/server' |
| 3 | +import { validateAirtableId, validateAlphanumericId } from '@/lib/core/security/input-validation' |
3 | 4 | import { getBaseUrl } from '@/lib/core/utils/urls' |
4 | 5 | import { getOAuthToken, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' |
5 | 6 |
|
@@ -358,6 +359,15 @@ export async function deleteAirtableWebhook( |
358 | 359 | return |
359 | 360 | } |
360 | 361 |
|
| 362 | + const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId') |
| 363 | + if (!baseIdValidation.isValid) { |
| 364 | + airtableLogger.warn(`[${requestId}] Invalid Airtable base ID format, skipping deletion`, { |
| 365 | + webhookId: webhook.id, |
| 366 | + baseId: baseId.substring(0, 20), |
| 367 | + }) |
| 368 | + return |
| 369 | + } |
| 370 | + |
361 | 371 | const userIdForToken = workflow.userId |
362 | 372 | const accessToken = await getOAuthToken(userIdForToken, 'airtable') |
363 | 373 | if (!accessToken) { |
@@ -428,6 +438,15 @@ export async function deleteAirtableWebhook( |
428 | 438 | return |
429 | 439 | } |
430 | 440 |
|
| 441 | + const webhookIdValidation = validateAirtableId(resolvedExternalId, 'ach', 'webhookId') |
| 442 | + if (!webhookIdValidation.isValid) { |
| 443 | + airtableLogger.warn(`[${requestId}] Invalid Airtable webhook ID format, skipping deletion`, { |
| 444 | + webhookId: webhook.id, |
| 445 | + externalId: resolvedExternalId.substring(0, 20), |
| 446 | + }) |
| 447 | + return |
| 448 | + } |
| 449 | + |
431 | 450 | const airtableDeleteUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks/${resolvedExternalId}` |
432 | 451 | const airtableResponse = await fetch(airtableDeleteUrl, { |
433 | 452 | method: 'DELETE', |
@@ -732,6 +751,14 @@ export async function deleteLemlistWebhook(webhook: any, requestId: string): Pro |
732 | 751 | const authString = Buffer.from(`:${apiKey}`).toString('base64') |
733 | 752 |
|
734 | 753 | const deleteById = async (id: string) => { |
| 754 | + const validation = validateAlphanumericId(id, 'Lemlist hook ID', 50) |
| 755 | + if (!validation.isValid) { |
| 756 | + lemlistLogger.warn(`[${requestId}] Invalid Lemlist hook ID format, skipping deletion`, { |
| 757 | + id: id.substring(0, 30), |
| 758 | + }) |
| 759 | + return |
| 760 | + } |
| 761 | + |
735 | 762 | const lemlistApiUrl = `https://api.lemlist.com/api/hooks/${id}` |
736 | 763 | const lemlistResponse = await fetch(lemlistApiUrl, { |
737 | 764 | method: 'DELETE', |
@@ -823,6 +850,24 @@ export async function deleteWebflowWebhook( |
823 | 850 | return |
824 | 851 | } |
825 | 852 |
|
| 853 | + const siteIdValidation = validateAlphanumericId(siteId, 'siteId', 100) |
| 854 | + if (!siteIdValidation.isValid) { |
| 855 | + webflowLogger.warn(`[${requestId}] Invalid Webflow site ID format, skipping deletion`, { |
| 856 | + webhookId: webhook.id, |
| 857 | + siteId: siteId.substring(0, 30), |
| 858 | + }) |
| 859 | + return |
| 860 | + } |
| 861 | + |
| 862 | + const webhookIdValidation = validateAlphanumericId(externalId, 'webhookId', 100) |
| 863 | + if (!webhookIdValidation.isValid) { |
| 864 | + webflowLogger.warn(`[${requestId}] Invalid Webflow webhook ID format, skipping deletion`, { |
| 865 | + webhookId: webhook.id, |
| 866 | + externalId: externalId.substring(0, 30), |
| 867 | + }) |
| 868 | + return |
| 869 | + } |
| 870 | + |
826 | 871 | const accessToken = await getOAuthToken(workflow.userId, 'webflow') |
827 | 872 | if (!accessToken) { |
828 | 873 | webflowLogger.warn( |
@@ -1122,6 +1167,16 @@ export async function createAirtableWebhookSubscription( |
1122 | 1167 | ) |
1123 | 1168 | } |
1124 | 1169 |
|
| 1170 | + const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId') |
| 1171 | + if (!baseIdValidation.isValid) { |
| 1172 | + throw new Error(baseIdValidation.error) |
| 1173 | + } |
| 1174 | + |
| 1175 | + const tableIdValidation = validateAirtableId(tableId, 'tbl', 'tableId') |
| 1176 | + if (!tableIdValidation.isValid) { |
| 1177 | + throw new Error(tableIdValidation.error) |
| 1178 | + } |
| 1179 | + |
1125 | 1180 | const accessToken = await getOAuthToken(userId, 'airtable') |
1126 | 1181 | if (!accessToken) { |
1127 | 1182 | airtableLogger.warn( |
@@ -1354,6 +1409,11 @@ export async function createWebflowWebhookSubscription( |
1354 | 1409 | throw new Error('Site ID is required to create Webflow webhook') |
1355 | 1410 | } |
1356 | 1411 |
|
| 1412 | + const siteIdValidation = validateAlphanumericId(siteId, 'siteId', 100) |
| 1413 | + if (!siteIdValidation.isValid) { |
| 1414 | + throw new Error(siteIdValidation.error) |
| 1415 | + } |
| 1416 | + |
1357 | 1417 | if (!triggerId) { |
1358 | 1418 | webflowLogger.warn(`[${requestId}] Missing triggerId for Webflow webhook creation.`, { |
1359 | 1419 | webhookId: webhookData.id, |
|
0 commit comments