Skip to content

Commit 3ada4a3

Browse files
authored
fix(chat): fail closed when embed gate cannot resolve workspace (#5046)
1 parent 7a33508 commit 3ada4a3

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

apps/sim/app/api/chat/utils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* @vitest-environment node
55
*/
66
import {
7+
dbChainMock,
8+
dbChainMockFns,
79
encryptionMock,
810
encryptionMockFns,
911
loggingSessionMock,
@@ -34,6 +36,8 @@ const {
3436
flagState: { isBillingEnabled: false, isFreeApiDeploymentGateEnabled: true },
3537
}))
3638

39+
vi.mock('@sim/db', () => dbChainMock)
40+
3741
vi.mock('@/lib/billing/core/api-access', () => ({
3842
isWorkspaceApiExecutionEntitled: mockIsWorkspaceApiExecutionEntitled,
3943
}))
@@ -480,6 +484,7 @@ describe('assertChatEmbedAllowed', () => {
480484
flagState.isBillingEnabled = true
481485
flagState.isFreeApiDeploymentGateEnabled = true
482486
mockIsWorkspaceApiExecutionEntitled.mockResolvedValue(true)
487+
dbChainMockFns.limit.mockResolvedValue([{ workspaceId: 'ws-1' }])
483488
})
484489

485490
it('returns 403 for a cross-site origin when the owner is on the free plan', async () => {
@@ -501,6 +506,17 @@ describe('assertChatEmbedAllowed', () => {
501506
expect(res).toBeNull()
502507
})
503508

509+
it('returns 403 for a cross-site origin when the workflow has no active workspace', async () => {
510+
dbChainMockFns.limit.mockResolvedValueOnce([])
511+
const res = await assertChatEmbedAllowed(
512+
chatRequest('https://evil.example.com'),
513+
'wf-1',
514+
'req-1'
515+
)
516+
expect(res?.status).toBe(403)
517+
expect(mockIsWorkspaceApiExecutionEntitled).not.toHaveBeenCalled()
518+
})
519+
504520
it('allows a first-party *.sim.ai origin without gating', async () => {
505521
const res = await assertChatEmbedAllowed(chatRequest('https://chat.sim.ai'), 'wf-1', 'req-1')
506522
expect(res).toBeNull()

apps/sim/app/api/chat/utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,14 @@ export async function assertChatEmbedAllowed(
8181
.where(and(eq(workflow.id, workflowId), isNull(workflow.archivedAt)))
8282
.limit(1)
8383

84-
if (!(await isWorkspaceApiExecutionEntitled(wf?.workspaceId ?? undefined))) {
84+
if (!wf?.workspaceId) {
85+
logger.warn(
86+
`[${requestId}] Chat embed blocked: no active workspace for workflow ${workflowId}, origin=${origin}`
87+
)
88+
return createErrorResponse('This chat is currently unavailable', 403)
89+
}
90+
91+
if (!(await isWorkspaceApiExecutionEntitled(wf.workspaceId))) {
8592
logger.warn(`[${requestId}] Chat embed blocked: workspace on free plan, origin=${origin}`)
8693
return createErrorResponse('Embedding this chat on external sites requires a paid plan', 403)
8794
}

0 commit comments

Comments
 (0)