Skip to content

Commit 415330c

Browse files
fix(slack): reject unsupported custom bot events on deploy
1 parent f14579f commit 415330c

2 files changed

Lines changed: 73 additions & 24 deletions

File tree

‎apps/sim/lib/webhooks/deploy.test.ts‎

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,12 @@ describe('resolveWebhookConfigForBlock — slack_oauth routing', () => {
305305
})
306306
}
307307

308-
it('routes a custom bot credential without the native app signing secret', async () => {
308+
it.each([
309+
'message',
310+
'app_context_changed',
311+
'agent_session_stopped',
312+
'agent_session_title_changed',
313+
])('routes custom-bot %s without the native app signing secret', async (eventType) => {
309314
setEnvFlags({ isSlackExtendedScopesEnabled: false })
310315
setEnv({ SLACK_SIGNING_SECRET: undefined })
311316
mockGetSlackBotCredential.mockResolvedValue({
@@ -316,7 +321,7 @@ describe('resolveWebhookConfigForBlock — slack_oauth routing', () => {
316321
signingSecret: 'secret',
317322
})
318323

319-
const result = await resolveSlack({ eventType: 'message', customBotCredential: 'cred_bot_1' })
324+
const result = await resolveSlack({ eventType, customBotCredential: 'cred_bot_1' })
320325

321326
expect(result?.success).toBe(true)
322327
if (!result?.success) throw new Error('expected success')
@@ -327,6 +332,31 @@ describe('resolveWebhookConfigForBlock — slack_oauth routing', () => {
327332
expect(mockFetchSlackTeamId).not.toHaveBeenCalled()
328333
})
329334

335+
it.each([
336+
['assistant_thread_started', 'customBotCredential'],
337+
['assistant_thread_context_changed', 'customBotCredential'],
338+
['assistant_thread_started', 'manualBotCredential'],
339+
['assistant_thread_context_changed', 'manualBotCredential'],
340+
])('rejects persisted custom-bot %s through %s before deployment', async (eventType, field) => {
341+
mockGetSlackBotCredential.mockResolvedValue({
342+
workspaceId: 'ws-1',
343+
botToken: 'xoxb-token',
344+
signingSecret: 'secret',
345+
})
346+
347+
const result = await resolveSlack({ eventType, [field]: 'cred_bot_1' })
348+
349+
expect(result?.success).toBe(false)
350+
if (result?.success) throw new Error('expected failure')
351+
expect(result?.error).toEqual({
352+
message:
353+
'Legacy Assistant events require a native Sim Slack connection. Choose an Agent View event for a custom bot.',
354+
status: 400,
355+
})
356+
expect(mockRefreshAccessTokenIfNeeded).not.toHaveBeenCalled()
357+
expect(mockFetchSlackTeamId).not.toHaveBeenCalled()
358+
})
359+
330360
it('deploys a slash command trigger and preserves its command filter', async () => {
331361
mockGetSlackBotCredential.mockResolvedValue({
332362
workspaceId: 'ws-1',
@@ -525,27 +555,34 @@ describe('resolveWebhookConfigForBlock — slack_oauth routing', () => {
525555
expect(mockRefreshAccessTokenIfNeeded).not.toHaveBeenCalled()
526556
})
527557

528-
it('routes an OAuth account by team_id on the slack_app provider', async () => {
529-
mockGetSlackBotCredential.mockResolvedValue(null)
530-
mockResolveOAuthAccountId.mockResolvedValue({ accountId: 'acct-1' })
531-
queueTableRows(credential, [{ id: 'cred_oauth_1' }])
532-
queueTableRows(account, [{ userId: 'owner-1' }])
533-
mockRefreshAccessTokenIfNeeded.mockResolvedValue('xoxb-token')
534-
mockFetchSlackTeamId.mockResolvedValue({ teamId: 'T123', userId: 'UBOT' })
535-
536-
const result = await resolveSlack({ eventType: 'message', customBotCredential: 'cred_oauth_1' })
537-
538-
expect(result?.success).toBe(true)
539-
if (!result?.success) throw new Error('expected success')
540-
expect(result.config.provider).toBe('slack_app')
541-
expect(result.config.routingKey).toBe('T123')
542-
expect(result.config.triggerPath).toBeNull()
543-
expect(result.config.providerConfig.bot_user_id).toBe('UBOT')
544-
// Runtime token resolution + disconnect cleanup key slack_app rows on this.
545-
expect(result.config.providerConfig.credentialId).toBe('cred_oauth_1')
546-
// Owner's token, not the deploying actor's.
547-
expect(mockRefreshAccessTokenIfNeeded).toHaveBeenCalledWith('cred_oauth_1', 'owner-1', 'req-1')
548-
})
558+
it.each(['message', 'assistant_thread_started', 'assistant_thread_context_changed'])(
559+
'routes native OAuth %s by team_id on the slack_app provider',
560+
async (eventType) => {
561+
mockGetSlackBotCredential.mockResolvedValue(null)
562+
mockResolveOAuthAccountId.mockResolvedValue({ accountId: 'acct-1' })
563+
queueTableRows(credential, [{ id: 'cred_oauth_1' }])
564+
queueTableRows(account, [{ userId: 'owner-1' }])
565+
mockRefreshAccessTokenIfNeeded.mockResolvedValue('xoxb-token')
566+
mockFetchSlackTeamId.mockResolvedValue({ teamId: 'T123', userId: 'UBOT' })
567+
568+
const result = await resolveSlack({ eventType, customBotCredential: 'cred_oauth_1' })
569+
570+
expect(result?.success).toBe(true)
571+
if (!result?.success) throw new Error('expected success')
572+
expect(result.config.provider).toBe('slack_app')
573+
expect(result.config.routingKey).toBe('T123')
574+
expect(result.config.triggerPath).toBeNull()
575+
expect(result.config.providerConfig.bot_user_id).toBe('UBOT')
576+
// Runtime token resolution + disconnect cleanup key slack_app rows on this.
577+
expect(result.config.providerConfig.credentialId).toBe('cred_oauth_1')
578+
// Owner's token, not the deploying actor's.
579+
expect(mockRefreshAccessTokenIfNeeded).toHaveBeenCalledWith(
580+
'cred_oauth_1',
581+
'owner-1',
582+
'req-1'
583+
)
584+
}
585+
)
549586

550587
it('fails when the connected Slack account token cannot be resolved', async () => {
551588
mockGetSlackBotCredential.mockResolvedValue(null)

‎apps/sim/lib/webhooks/deploy.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import type { SubBlockConfig } from '@/blocks/types'
4949
import type { BlockState } from '@/stores/workflows/workflow/types'
5050
import { getTrigger, isTriggerValid } from '@/triggers'
5151
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'
52-
import { SIM_SUBSCRIBED_EVENTS } from '@/triggers/slack/shared'
52+
import { SIM_SUBSCRIBED_EVENTS, slackEventById } from '@/triggers/slack/shared'
5353
import { resolveBlockTriggerId } from '@/triggers/webhook-url'
5454

5555
const logger = createLogger('DeployWebhookSync')
@@ -452,6 +452,18 @@ export async function resolveWebhookConfigForBlock(input: {
452452
},
453453
}
454454
}
455+
const eventType =
456+
typeof providerConfig.eventType === 'string' ? providerConfig.eventType : null
457+
if (eventType && slackEventById.get(eventType)?.legacy) {
458+
return {
459+
success: false,
460+
error: {
461+
message:
462+
'Legacy Assistant events require a native Sim Slack connection. Choose an Agent View event for a custom bot.',
463+
status: 400,
464+
},
465+
}
466+
}
455467
try {
456468
replaceSlackStreamAuthoringConfig(
457469
providerConfig,

0 commit comments

Comments
 (0)