diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index f7c446869a8..a42d2d6ddf0 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express' import sanitizeHtml from 'sanitize-html' -import { isPredictionRequest, extractChatflowId, validateChatflowDomain } from './domainValidation' +import { extractChatflowId, validateChatflowDomain, isPublicChatflowRequest } from './domainValidation' export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void { // decoding is necessary as the url is encoded by the browser @@ -43,7 +43,7 @@ export function getCorsOptions(): any { const corsOptions = { origin: async (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) => { const allowedOrigins = getAllowedCorsOrigins() - const isPredictionReq = isPredictionRequest(req.url) + const isPublicChatflowReq = isPublicChatflowRequest(req.url) const allowedList = parseAllowedOrigins(allowedOrigins) const originLc = origin?.toLowerCase() @@ -53,7 +53,7 @@ export function getCorsOptions(): any { // Global allow: '*' or exact match const globallyAllowed = allowedOrigins === '*' || allowedList.includes(originLc) - if (isPredictionReq) { + if (isPublicChatflowReq) { // Per-chatflow allowlist OR globally allowed const chatflowId = extractChatflowId(req.url) let chatflowAllowed = false diff --git a/packages/server/src/utils/domainValidation.ts b/packages/server/src/utils/domainValidation.ts index a2482d8bbec..ec4c84c6188 100644 --- a/packages/server/src/utils/domainValidation.ts +++ b/packages/server/src/utils/domainValidation.ts @@ -2,6 +2,13 @@ import { isValidUUID } from 'flowise-components' import chatflowsService from '../services/chatflows' import logger from './logger' +// List of allowed URL slugs for public access to chatbots +// It assumes the URL format includes one of the following patterns: +// /prediction/{chatflowId}. +// /public-chatbotConfig/{chatflowId} +// /chatflows-streaming/{chatflowId} +const ALLOWED_SLUGS = ['/prediction/', '/public-chatbotConfig/', '/chatflows-streaming/'] + /** * Validates if the origin is allowed for a specific chatflow * @param chatflowId - The chatflow ID to validate against @@ -58,10 +65,12 @@ async function validateChatflowDomain(chatflowId: string, origin: string, worksp function extractChatflowId(url: string): string | null { try { const urlParts = url.split('/') - const predictionIndex = urlParts.indexOf('prediction') + const slug = extractSlugFromUrl(url) + if (!slug) return null + const slugIndex = urlParts.indexOf(slug) - if (predictionIndex !== -1 && urlParts.length > predictionIndex + 1) { - const chatflowId = urlParts[predictionIndex + 1] + if (slugIndex !== -1 && urlParts.length > slugIndex + 1) { + const chatflowId = urlParts[slugIndex + 1] // Remove query parameters if present return chatflowId.split('?')[0] } @@ -74,12 +83,26 @@ function extractChatflowId(url: string): string | null { } /** - * Validates if a request is a prediction request + * Extracts the slug from the URL if it matches any of the allowed slugs + * @param url - The request URL + * @returns string | null - The matched slug or null if no match + */ +function extractSlugFromUrl(url: string): string | null { + for (const publicUrl of ALLOWED_SLUGS) { + if (url.includes(publicUrl)) { + return publicUrl.replace(/\//g, '') // remove slashes + } + } + return null +} + +/** + * Validates if a request is for public chatflows (embedded chatbots) * @param url - The request URL - * @returns boolean - True if it's a prediction request + * @returns boolean - True if it's a public chatflow request */ -function isPredictionRequest(url: string): boolean { - return url.includes('/prediction/') +function isPublicChatflowRequest(url: string): boolean { + return extractSlugFromUrl(url) !== null } /** @@ -106,4 +129,4 @@ async function getUnauthorizedOriginError(chatflowId: string, workspaceId?: stri } } -export { isPredictionRequest, extractChatflowId, validateChatflowDomain, getUnauthorizedOriginError } +export { isPublicChatflowRequest, extractChatflowId, validateChatflowDomain, getUnauthorizedOriginError }