( request: NextRequest, workflowId: string, requestId: string )
| 49 | * gated. Returns a 403 response to short-circuit the route, or `null` to allow. |
| 50 | */ |
| 51 | export async function assertChatEmbedAllowed( |
| 52 | request: NextRequest, |
| 53 | workflowId: string, |
| 54 | requestId: string |
| 55 | ): Promise<NextResponse | null> { |
| 56 | if (!isBillingEnabled || !isFreeApiDeploymentGateEnabled) return null |
| 57 | |
| 58 | const origin = request.headers.get('origin') |
| 59 | if (!origin || isFirstPartyOrigin(origin)) return null |
| 60 | |
| 61 | const [wf] = await db |
| 62 | .select({ workspaceId: workflow.workspaceId }) |
| 63 | .from(workflow) |
| 64 | .where(and(eq(workflow.id, workflowId), isNull(workflow.archivedAt))) |
| 65 | .limit(1) |
| 66 | |
| 67 | if (!wf?.workspaceId) { |
| 68 | logger.warn( |
| 69 | `[${requestId}] Chat embed blocked: no active workspace for workflow ${workflowId}, origin=${origin}` |
| 70 | ) |
| 71 | return createErrorResponse('This chat is currently unavailable', 403) |
| 72 | } |
| 73 | |
| 74 | if (!(await isWorkspaceApiExecutionEntitled(wf.workspaceId))) { |
| 75 | logger.warn(`[${requestId}] Chat embed blocked: workspace on free plan, origin=${origin}`) |
| 76 | return createErrorResponse('Embedding this chat on external sites requires a paid plan', 403) |
| 77 | } |
| 78 | |
| 79 | return null |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Check if user has permission to create a chat for a specific workflow |
no test coverage detected