diff --git a/packages/server/src/controllers/text-to-speech/index.ts b/packages/server/src/controllers/text-to-speech/index.ts index 7664a2a77e0..6e8bb01baa2 100644 --- a/packages/server/src/controllers/text-to-speech/index.ts +++ b/packages/server/src/controllers/text-to-speech/index.ts @@ -89,8 +89,6 @@ const generateTextToSpeech = async (req: Request, res: Response) => { res.setHeader('Content-Type', 'text/event-stream') res.setHeader('Cache-Control', 'no-cache') res.setHeader('Connection', 'keep-alive') - res.setHeader('Access-Control-Allow-Origin', '*') - res.setHeader('Access-Control-Allow-Headers', 'Cache-Control') const appServer = getRunningExpressApp() const options = { diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index a42d2d6ddf0..180e44584ad 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 { extractChatflowId, validateChatflowDomain, isPublicChatflowRequest } from './domainValidation' +import { extractChatflowId, validateChatflowDomain, isPublicChatflowRequest, isTTSGenerateRequest } from './domainValidation' export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void { // decoding is necessary as the url is encoded by the browser @@ -44,6 +44,7 @@ export function getCorsOptions(): any { origin: async (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) => { const allowedOrigins = getAllowedCorsOrigins() const isPublicChatflowReq = isPublicChatflowRequest(req.url) + const isTTSReq = isTTSGenerateRequest(req.url) const allowedList = parseAllowedOrigins(allowedOrigins) const originLc = origin?.toLowerCase() @@ -53,9 +54,10 @@ export function getCorsOptions(): any { // Global allow: '*' or exact match const globallyAllowed = allowedOrigins === '*' || allowedList.includes(originLc) - if (isPublicChatflowReq) { + if (isPublicChatflowReq || isTTSReq) { // Per-chatflow allowlist OR globally allowed - const chatflowId = extractChatflowId(req.url) + // TTS generate passes chatflowId in the request body, not the URL path + const chatflowId = isTTSReq ? req.body?.chatflowId : extractChatflowId(req.url) let chatflowAllowed = false if (chatflowId) { try { @@ -65,6 +67,9 @@ export function getCorsOptions(): any { console.error('Domain validation error:', error) chatflowAllowed = false } + } else if (isTTSReq) { + // OPTIONS preflight has no body — allow it through so the actual POST can be validated with chatflowId + chatflowAllowed = true } return originCallback(null, globallyAllowed || chatflowAllowed) } diff --git a/packages/server/src/utils/domainValidation.ts b/packages/server/src/utils/domainValidation.ts index ec4c84c6188..60372fbeff1 100644 --- a/packages/server/src/utils/domainValidation.ts +++ b/packages/server/src/utils/domainValidation.ts @@ -9,6 +9,9 @@ import logger from './logger' // /chatflows-streaming/{chatflowId} const ALLOWED_SLUGS = ['/prediction/', '/public-chatbotConfig/', '/chatflows-streaming/'] +// The TTS generate endpoint passes chatflowId in the request body, not the URL path +const TTS_GENERATE_PATH = '/api/v1/text-to-speech/generate' + /** * Validates if the origin is allowed for a specific chatflow * @param chatflowId - The chatflow ID to validate against @@ -105,6 +108,16 @@ function isPublicChatflowRequest(url: string): boolean { return extractSlugFromUrl(url) !== null } +/** + * Checks if the request is for the TTS generate endpoint. + * This endpoint passes chatflowId in the request body rather than the URL path. + * @param url - The request URL + * @returns boolean - True if it's the TTS generate endpoint + */ +function isTTSGenerateRequest(url: string): boolean { + return url.split('?')[0] === TTS_GENERATE_PATH +} + /** * Get the custom error message for unauthorized origin * @param chatflowId - The chatflow ID @@ -129,4 +142,4 @@ async function getUnauthorizedOriginError(chatflowId: string, workspaceId?: stri } } -export { isPublicChatflowRequest, extractChatflowId, validateChatflowDomain, getUnauthorizedOriginError } +export { isPublicChatflowRequest, isTTSGenerateRequest, extractChatflowId, validateChatflowDomain, getUnauthorizedOriginError }