Skip to content

Commit 2a790d8

Browse files
committed
fix for backwards compat
1 parent f0f31d8 commit 2a790d8

4 files changed

Lines changed: 138 additions & 96 deletions

File tree

apps/sim/app/api/webhooks/route.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ export async function POST(request: NextRequest) {
329329
logger.info(`[${requestId}] Gmail provider detected. Setting up Gmail webhook configuration.`)
330330
try {
331331
const { configureGmailPolling } = await import('@/lib/webhooks/utils')
332-
// Strict: utils will resolve credential ownership; do not fall back to workflow owner
333-
const success = await configureGmailPolling('', savedWebhook, requestId)
332+
// Pass workflow owner for backward-compat fallback (utils prefers credentialId if present)
333+
const success = await configureGmailPolling(workflowRecord.userId, savedWebhook, requestId)
334334

335335
if (!success) {
336336
logger.error(`[${requestId}] Failed to configure Gmail polling`)
@@ -364,8 +364,12 @@ export async function POST(request: NextRequest) {
364364
)
365365
try {
366366
const { configureOutlookPolling } = await import('@/lib/webhooks/utils')
367-
// Strict: utils will resolve credential ownership; do not fall back to workflow owner
368-
const success = await configureOutlookPolling('', savedWebhook, requestId)
367+
// Pass workflow owner for backward-compat fallback (utils prefers credentialId if present)
368+
const success = await configureOutlookPolling(
369+
workflowRecord.userId,
370+
savedWebhook,
371+
requestId
372+
)
369373

370374
if (!success) {
371375
logger.error(`[${requestId}] Failed to configure Outlook polling`)

apps/sim/lib/webhooks/gmail-polling-service.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { nanoid } from 'nanoid'
33
import { Logger } from '@/lib/logs/console/logger'
44
import { hasProcessedMessage, markMessageAsProcessed } from '@/lib/redis'
55
import { getBaseUrl } from '@/lib/urls/utils'
6-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
6+
import { getOAuthToken, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
77
import { db } from '@/db'
88
import { account, webhook } from '@/db/schema'
99

@@ -84,26 +84,33 @@ export async function pollGmailWebhooks() {
8484
// Extract metadata
8585
const metadata = webhookData.providerConfig as any
8686
const credentialId: string | undefined = metadata?.credentialId
87+
const userId: string | undefined = metadata?.userId
8788

88-
if (!credentialId) {
89-
logger.error(`[${requestId}] Missing credentialId for webhook ${webhookId}`)
90-
return { success: false, webhookId, error: 'Missing credentialId' }
89+
if (!credentialId && !userId) {
90+
logger.error(`[${requestId}] Missing credentialId and userId for webhook ${webhookId}`)
91+
return { success: false, webhookId, error: 'Missing credentialId and userId' }
9192
}
9293

93-
// Resolve owner and token strictly via credentialId
94-
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
95-
if (rows.length === 0) {
96-
logger.error(
97-
`[${requestId}] Credential ${credentialId} not found for webhook ${webhookId}`
98-
)
99-
return { success: false, webhookId, error: 'Credential not found' }
94+
// Resolve owner and token
95+
let accessToken: string | null = null
96+
if (credentialId) {
97+
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
98+
if (rows.length === 0) {
99+
logger.error(
100+
`[${requestId}] Credential ${credentialId} not found for webhook ${webhookId}`
101+
)
102+
return { success: false, webhookId, error: 'Credential not found' }
103+
}
104+
const ownerUserId = rows[0].userId
105+
accessToken = await refreshAccessTokenIfNeeded(credentialId, ownerUserId, requestId)
106+
} else if (userId) {
107+
// Backward-compat fallback to workflow owner token
108+
accessToken = await getOAuthToken(userId, 'google-email')
100109
}
101-
const ownerUserId = rows[0].userId
102110

103-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, ownerUserId, requestId)
104111
if (!accessToken) {
105112
logger.error(
106-
`[${requestId}] Failed to get Gmail access token for webhook ${webhookId} via credential ${credentialId}`
113+
`[${requestId}] Failed to get Gmail access token for webhook ${webhookId} (cred or fallback)`
107114
)
108115
return { success: false, webhookId, error: 'No access token' }
109116
}

apps/sim/lib/webhooks/outlook-polling-service.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { nanoid } from 'nanoid'
33
import { Logger } from '@/lib/logs/console/logger'
44
import { hasProcessedMessage, markMessageAsProcessed } from '@/lib/redis'
55
import { getBaseUrl } from '@/lib/urls/utils'
6-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
6+
import { getOAuthToken, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
77
import { db } from '@/db'
88
import { account, webhook } from '@/db/schema'
99

@@ -108,29 +108,36 @@ export async function pollOutlookWebhooks() {
108108
try {
109109
logger.info(`[${requestId}] Processing Outlook webhook: ${webhookId}`)
110110

111-
// Extract credentialId from providerConfig (strict)
111+
// Extract credentialId and/or userId
112112
const metadata = webhookData.providerConfig as any
113113
const credentialId: string | undefined = metadata?.credentialId
114+
const userId: string | undefined = metadata?.userId
114115

115-
if (!credentialId) {
116-
logger.error(`[${requestId}] Missing credentialId for webhook ${webhookId}`)
117-
return { success: false, webhookId, error: 'Missing credentialId' }
116+
if (!credentialId && !userId) {
117+
logger.error(`[${requestId}] Missing credentialId and userId for webhook ${webhookId}`)
118+
return { success: false, webhookId, error: 'Missing credentialId and userId' }
118119
}
119120

120-
// Resolve owner and token via credentialId
121-
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
122-
if (!rows.length) {
123-
logger.error(
124-
`[${requestId}] Credential ${credentialId} not found for webhook ${webhookId}`
125-
)
126-
return { success: false, webhookId, error: 'Credential not found' }
121+
// Resolve access token
122+
let accessToken: string | null = null
123+
if (credentialId) {
124+
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
125+
if (!rows.length) {
126+
logger.error(
127+
`[${requestId}] Credential ${credentialId} not found for webhook ${webhookId}`
128+
)
129+
return { success: false, webhookId, error: 'Credential not found' }
130+
}
131+
const ownerUserId = rows[0].userId
132+
accessToken = await refreshAccessTokenIfNeeded(credentialId, ownerUserId, requestId)
133+
} else if (userId) {
134+
// Backward-compat fallback to workflow owner token
135+
accessToken = await getOAuthToken(userId, 'outlook')
127136
}
128-
const ownerUserId = rows[0].userId
129137

130-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, ownerUserId, requestId)
131138
if (!accessToken) {
132139
logger.error(
133-
`[${requestId}] Failed to get Outlook access token for webhook ${webhookId} via credential ${credentialId}`
140+
`[${requestId}] Failed to get Outlook access token for webhook ${webhookId} (cred or fallback)`
134141
)
135142
return { success: false, webhookId, error: 'No access token' }
136143
}

apps/sim/lib/webhooks/utils.ts

Lines changed: 87 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { and, eq } from 'drizzle-orm'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { createLogger } from '@/lib/logs/console/logger'
4-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
4+
import { getOAuthToken, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
55
import { db } from '@/db'
66
import { account, webhook } from '@/db/schema'
77

@@ -1284,29 +1284,42 @@ export async function configureGmailPolling(
12841284
const providerConfig = (webhookData.providerConfig as Record<string, any>) || {}
12851285

12861286
const credentialId: string | undefined = providerConfig.credentialId
1287-
if (!credentialId) {
1288-
logger.error(
1289-
`[${requestId}] Missing credentialId for Gmail webhook ${webhookData.id}. Refusing to proceed.`
1290-
)
1291-
return false
1292-
}
12931287

1294-
// Resolve owner user ID from the credential and refresh token if needed
1295-
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
1296-
if (rows.length === 0) {
1297-
logger.error(
1298-
`[${requestId}] Credential ${credentialId} not found for Gmail webhook ${webhookData.id}`
1299-
)
1300-
return false
1301-
}
1302-
const ownerUserId = rows[0].userId
1288+
let effectiveUserId: string | null = null
1289+
let accessToken: string | null = null
13031290

1304-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, ownerUserId, requestId)
1305-
if (!accessToken) {
1306-
logger.error(
1307-
`[${requestId}] Failed to refresh/access Gmail token for credential ${credentialId}`
1308-
)
1309-
return false
1291+
if (credentialId) {
1292+
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
1293+
if (rows.length === 0) {
1294+
logger.error(
1295+
`[${requestId}] Credential ${credentialId} not found for Gmail webhook ${webhookData.id}`
1296+
)
1297+
return false
1298+
}
1299+
effectiveUserId = rows[0].userId
1300+
accessToken = await refreshAccessTokenIfNeeded(credentialId, effectiveUserId, requestId)
1301+
if (!accessToken) {
1302+
logger.error(
1303+
`[${requestId}] Failed to refresh/access Gmail token for credential ${credentialId}`
1304+
)
1305+
return false
1306+
}
1307+
} else {
1308+
// Backward-compat: fall back to workflow owner
1309+
if (!userId) {
1310+
logger.error(
1311+
`[${requestId}] Missing credentialId and userId for Gmail webhook ${webhookData.id}`
1312+
)
1313+
return false
1314+
}
1315+
effectiveUserId = userId
1316+
accessToken = await getOAuthToken(effectiveUserId, 'google-email')
1317+
if (!accessToken) {
1318+
logger.error(
1319+
`[${requestId}] Failed to obtain Gmail token for user ${effectiveUserId} (fallback)`
1320+
)
1321+
return false
1322+
}
13101323
}
13111324

13121325
const maxEmailsPerPoll =
@@ -1326,8 +1339,8 @@ export async function configureGmailPolling(
13261339
.set({
13271340
providerConfig: {
13281341
...providerConfig,
1329-
userId: ownerUserId,
1330-
credentialId,
1342+
userId: effectiveUserId,
1343+
...(credentialId ? { credentialId } : {}),
13311344
maxEmailsPerPoll,
13321345
pollingInterval,
13331346
markAsRead: providerConfig.markAsRead || false,
@@ -1371,56 +1384,67 @@ export async function configureOutlookPolling(
13711384
const providerConfig = (webhookData.providerConfig as Record<string, any>) || {}
13721385

13731386
const credentialId: string | undefined = providerConfig.credentialId
1374-
if (!credentialId) {
1375-
logger.error(
1376-
`[${requestId}] Missing credentialId for Outlook webhook ${webhookData.id}. Refusing to proceed.`
1377-
)
1378-
return false
1379-
}
13801387

1381-
// Resolve owner user ID from the credential and refresh token if needed
1382-
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
1383-
if (rows.length === 0) {
1384-
logger.error(
1385-
`[${requestId}] Credential ${credentialId} not found for Outlook webhook ${webhookData.id}`
1386-
)
1387-
return false
1388-
}
1389-
const ownerUserId = rows[0].userId
1388+
let effectiveUserId: string | null = null
1389+
let accessToken: string | null = null
13901390

1391-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, ownerUserId, requestId)
1392-
if (!accessToken) {
1393-
logger.error(
1394-
`[${requestId}] Failed to refresh/access Outlook token for credential ${credentialId}`
1395-
)
1396-
return false
1391+
if (credentialId) {
1392+
const rows = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
1393+
if (rows.length === 0) {
1394+
logger.error(
1395+
`[${requestId}] Credential ${credentialId} not found for Outlook webhook ${webhookData.id}`
1396+
)
1397+
return false
1398+
}
1399+
effectiveUserId = rows[0].userId
1400+
accessToken = await refreshAccessTokenIfNeeded(credentialId, effectiveUserId, requestId)
1401+
if (!accessToken) {
1402+
logger.error(
1403+
`[${requestId}] Failed to refresh/access Outlook token for credential ${credentialId}`
1404+
)
1405+
return false
1406+
}
1407+
} else {
1408+
// Backward-compat: fall back to workflow owner
1409+
if (!userId) {
1410+
logger.error(
1411+
`[${requestId}] Missing credentialId and userId for Outlook webhook ${webhookData.id}`
1412+
)
1413+
return false
1414+
}
1415+
effectiveUserId = userId
1416+
accessToken = await getOAuthToken(effectiveUserId, 'outlook')
1417+
if (!accessToken) {
1418+
logger.error(
1419+
`[${requestId}] Failed to obtain Outlook token for user ${effectiveUserId} (fallback)`
1420+
)
1421+
return false
1422+
}
13971423
}
13981424

1399-
const maxEmailsPerPoll =
1400-
typeof providerConfig.maxEmailsPerPoll === 'string'
1401-
? Number.parseInt(providerConfig.maxEmailsPerPoll, 10) || 25
1402-
: providerConfig.maxEmailsPerPoll || 25
1403-
1404-
const pollingInterval =
1405-
typeof providerConfig.pollingInterval === 'string'
1406-
? Number.parseInt(providerConfig.pollingInterval, 10) || 5
1407-
: providerConfig.pollingInterval || 5
1425+
const providerCfg = (webhookData.providerConfig as Record<string, any>) || {}
14081426

14091427
const now = new Date()
14101428

14111429
await db
14121430
.update(webhook)
14131431
.set({
14141432
providerConfig: {
1415-
...providerConfig,
1416-
userId: ownerUserId,
1417-
credentialId,
1418-
maxEmailsPerPoll,
1419-
pollingInterval,
1420-
markAsRead: providerConfig.markAsRead || false,
1421-
includeRawEmail: providerConfig.includeRawEmail || false,
1422-
folderIds: providerConfig.folderIds || ['inbox'],
1423-
folderFilterBehavior: providerConfig.folderFilterBehavior || 'INCLUDE',
1433+
...providerCfg,
1434+
userId: effectiveUserId,
1435+
...(credentialId ? { credentialId } : {}),
1436+
maxEmailsPerPoll:
1437+
typeof providerCfg.maxEmailsPerPoll === 'string'
1438+
? Number.parseInt(providerCfg.maxEmailsPerPoll, 10) || 25
1439+
: providerCfg.maxEmailsPerPoll || 25,
1440+
pollingInterval:
1441+
typeof providerCfg.pollingInterval === 'string'
1442+
? Number.parseInt(providerCfg.pollingInterval, 10) || 5
1443+
: providerCfg.pollingInterval || 5,
1444+
markAsRead: providerCfg.markAsRead || false,
1445+
includeRawEmail: providerCfg.includeRawEmail || false,
1446+
folderIds: providerCfg.folderIds || ['inbox'],
1447+
folderFilterBehavior: providerCfg.folderFilterBehavior || 'INCLUDE',
14241448
lastCheckedTimestamp: now.toISOString(),
14251449
setupCompleted: true,
14261450
},

0 commit comments

Comments
 (0)