From 156ab31fbd2ab3eb1b544905ccbc04be5ab3e194 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Sun, 20 Jul 2025 17:02:26 +0530 Subject: [PATCH 01/10] feat: Require workspace ID for API key operations - Added validation to ensure `activeWorkspaceId` is present in user requests for all API key operations (get, create, update, import, delete). - Updated `getWorkspaceSearchOptions` and `getWorkspaceSearchOptionsFromReq` to throw an error if `workspaceId` is not provided. - Modified service methods to enforce `workspaceId` as a required parameter for database operations related to API keys. --- packages/server/src/controllers/apikey/index.ts | 15 +++++++++++++++ .../enterprise/utils/ControllerServiceUtils.ts | 12 ++++++++++-- packages/server/src/services/apikey/index.ts | 15 ++++++++------- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/server/src/controllers/apikey/index.ts b/packages/server/src/controllers/apikey/index.ts index 340ff27b251..a13f02f7f20 100644 --- a/packages/server/src/controllers/apikey/index.ts +++ b/packages/server/src/controllers/apikey/index.ts @@ -9,6 +9,9 @@ const getAllApiKeys = async (req: Request, res: Response, next: NextFunction) => try { const autoCreateNewKey = true const { page, limit } = getPageAndLimitParams(req) + if (!req.user?.activeWorkspaceId) { + throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Workspace ID is required`) + } const apiResponse = await apikeyService.getAllApiKeys(req.user?.activeWorkspaceId, autoCreateNewKey, page, limit) return res.json(apiResponse) } catch (error) { @@ -21,6 +24,9 @@ const createApiKey = async (req: Request, res: Response, next: NextFunction) => if (typeof req.body === 'undefined' || !req.body.keyName) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.createApiKey - keyName not provided!`) } + if (!req.user?.activeWorkspaceId) { + throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Workspace ID is required`) + } const apiResponse = await apikeyService.createApiKey(req.body.keyName, req.user?.activeWorkspaceId) return res.json(apiResponse) } catch (error) { @@ -36,6 +42,9 @@ const updateApiKey = async (req: Request, res: Response, next: NextFunction) => } if (typeof req.body === 'undefined' || !req.body.keyName) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.updateApiKey - keyName not provided!`) + } + if (!req.user?.activeWorkspaceId) { + throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Workspace ID is required`) } const apiResponse = await apikeyService.updateApiKey(req.params.id, req.body.keyName, req.user?.activeWorkspaceId) return res.json(apiResponse) @@ -50,6 +59,9 @@ const importKeys = async (req: Request, res: Response, next: NextFunction) => { if (typeof req.body === 'undefined' || !req.body.jsonFile) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.importKeys - body not provided!`) } + if (!req.user?.activeWorkspaceId) { + throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Workspace ID is required`) + } req.body.workspaceId = req.user?.activeWorkspaceId const apiResponse = await apikeyService.importKeys(req.body) return res.json(apiResponse) @@ -64,6 +76,9 @@ const deleteApiKey = async (req: Request, res: Response, next: NextFunction) => if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.deleteApiKey - id not provided!`) } + if (!req.user?.activeWorkspaceId) { + throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Workspace ID is required`) + } const apiResponse = await apikeyService.deleteApiKey(req.params.id, req.user?.activeWorkspaceId) return res.json(apiResponse) } catch (error) { diff --git a/packages/server/src/enterprise/utils/ControllerServiceUtils.ts b/packages/server/src/enterprise/utils/ControllerServiceUtils.ts index 245f1e4711e..1d0983c6761 100644 --- a/packages/server/src/enterprise/utils/ControllerServiceUtils.ts +++ b/packages/server/src/enterprise/utils/ControllerServiceUtils.ts @@ -1,11 +1,19 @@ import { Equal } from 'typeorm' import { Request } from 'express' +import { InternalFlowiseError } from '../../errors/internalFlowiseError' +import { StatusCodes } from 'http-status-codes' export const getWorkspaceSearchOptions = (workspaceId?: string) => { - return workspaceId ? { workspaceId: Equal(workspaceId) } : {} + if (!workspaceId) { + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, `Workspace ID is required`) + } + return { workspaceId: Equal(workspaceId) } } export const getWorkspaceSearchOptionsFromReq = (req: Request) => { const workspaceId = req.user?.activeWorkspaceId - return workspaceId ? { workspaceId: Equal(workspaceId) } : {} + if (!workspaceId) { + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, `Workspace ID is required`) + } + return { workspaceId: Equal(workspaceId) } } diff --git a/packages/server/src/services/apikey/index.ts b/packages/server/src/services/apikey/index.ts index a8b6036333a..5e009c92774 100644 --- a/packages/server/src/services/apikey/index.ts +++ b/packages/server/src/services/apikey/index.ts @@ -9,14 +9,14 @@ import { Not, IsNull } from 'typeorm' import { getWorkspaceSearchOptions } from '../../enterprise/utils/ControllerServiceUtils' import { v4 as uuidv4 } from 'uuid' -const getAllApiKeysFromDB = async (workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllApiKeysFromDB = async (workspaceId: string, page: number = -1, limit: number = -1) => { const appServer = getRunningExpressApp() const queryBuilder = appServer.AppDataSource.getRepository(ApiKey).createQueryBuilder('api_key').orderBy('api_key.updatedDate', 'DESC') if (page > 0 && limit > 0) { queryBuilder.skip((page - 1) * limit) queryBuilder.take(limit) } - if (workspaceId) queryBuilder.andWhere('api_key.workspaceId = :workspaceId', { workspaceId }) + queryBuilder.andWhere('api_key.workspaceId = :workspaceId', { workspaceId }) const [data, total] = await queryBuilder.getManyAndCount() const keysWithChatflows = await addChatflowsCount(data) @@ -27,7 +27,7 @@ const getAllApiKeysFromDB = async (workspaceId?: string, page: number = -1, limi } } -const getAllApiKeys = async (workspaceId?: string, autoCreateNewKey?: boolean, page: number = -1, limit: number = -1) => { +const getAllApiKeys = async (workspaceId: string, autoCreateNewKey?: boolean, page: number = -1, limit: number = -1) => { try { let keys = await getAllApiKeysFromDB(workspaceId, page, limit) const isEmpty = keys?.total === 0 || (Array.isArray(keys) && keys?.length === 0) @@ -71,7 +71,7 @@ const getApiKeyById = async (apiKeyId: string) => { } } -const createApiKey = async (keyName: string, workspaceId?: string) => { +const createApiKey = async (keyName: string, workspaceId: string) => { try { const apiKey = generateAPIKey() const apiSecret = generateSecretHash(apiKey) @@ -91,11 +91,12 @@ const createApiKey = async (keyName: string, workspaceId?: string) => { } // Update api key -const updateApiKey = async (id: string, keyName: string, workspaceId?: string) => { +const updateApiKey = async (id: string, keyName: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!currentKey) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `ApiKey ${currentKey} not found`) @@ -108,7 +109,7 @@ const updateApiKey = async (id: string, keyName: string, workspaceId?: string) = } } -const deleteApiKey = async (id: string, workspaceId?: string) => { +const deleteApiKey = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const dbResponse = await appServer.AppDataSource.getRepository(ApiKey).delete({ id, workspaceId }) From 0d1c238807031e8b49075c2450c3248f94005461 Mon Sep 17 00:00:00 2001 From: Vinod Kiran Date: Wed, 30 Jul 2025 10:27:41 +0530 Subject: [PATCH 02/10] feat: Enforce workspace ID as a required field across multiple interfaces and services - Updated various interfaces to make `workspaceId` a mandatory field instead of optional. - Enhanced assistant and export-import service methods to require `workspaceId` for operations, ensuring proper validation and error handling. - Modified database entity definitions to reflect the change in `workspaceId` from optional to required. - Improved error handling in controllers to check for `activeWorkspaceId` before proceeding with requests. --- packages/server/src/Interface.ts | 18 ++++---- .../src/controllers/assistants/index.ts | 45 ++++++++++++++++--- .../src/controllers/export-import/index.ts | 12 +++-- .../server/src/database/entities/ApiKey.ts | 4 +- .../server/src/database/entities/Assistant.ts | 4 +- .../server/src/database/entities/ChatFlow.ts | 4 +- .../src/database/entities/Credential.ts | 4 +- .../src/database/entities/CustomTemplate.ts | 2 +- .../server/src/database/entities/Dataset.ts | 4 +- .../src/database/entities/DocumentStore.ts | 4 +- .../src/database/entities/Evaluation.ts | 4 +- .../server/src/database/entities/Evaluator.ts | 4 +- .../server/src/database/entities/Execution.ts | 4 +- packages/server/src/database/entities/Tool.ts | 4 +- .../server/src/database/entities/Variable.ts | 4 +- .../server/src/services/assistants/index.ts | 21 +++++---- .../src/services/export-import/index.ts | 8 ++-- packages/server/src/utils/index.ts | 3 +- 18 files changed, 98 insertions(+), 55 deletions(-) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 97f66d10920..3e72217c46a 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -69,7 +69,7 @@ export interface IChatFlow { apiConfig?: string category?: string type?: ChatflowType - workspaceId?: string + workspaceId: string } export interface IChatMessage { @@ -114,7 +114,7 @@ export interface ITool { func?: string updatedDate: Date createdDate: Date - workspaceId?: string + workspaceId: string } export interface IAssistant { @@ -124,7 +124,7 @@ export interface IAssistant { iconSrc?: string updatedDate: Date createdDate: Date - workspaceId?: string + workspaceId: string } export interface ICredential { @@ -134,7 +134,7 @@ export interface ICredential { encryptedData: string updatedDate: Date createdDate: Date - workspaceId?: string + workspaceId: string } export interface IVariable { @@ -144,7 +144,7 @@ export interface IVariable { type: string updatedDate: Date createdDate: Date - workspaceId?: string + workspaceId: string } export interface ILead { @@ -176,7 +176,7 @@ export interface IExecution { createdDate: Date updatedDate: Date stoppedDate: Date - workspaceId?: string + workspaceId: string } export interface IComponentNodes { @@ -332,7 +332,7 @@ export interface ICredentialReqBody { name: string credentialName: string plainDataObj: ICredentialDataDecrypted - workspaceId?: string + workspaceId: string } // Decrypted credential object sent back to client @@ -351,7 +351,7 @@ export interface IApiKey { apiKey: string apiSecret: string updatedDate: Date - workspaceId?: string + workspaceId: string } export interface ICustomTemplate { @@ -365,7 +365,7 @@ export interface ICustomTemplate { badge?: string framework?: string usecases?: string - workspaceId?: string + workspaceId: string } export interface IFlowConfig { diff --git a/packages/server/src/controllers/assistants/index.ts b/packages/server/src/controllers/assistants/index.ts index 324907d0b2c..e4159bf3fa8 100644 --- a/packages/server/src/controllers/assistants/index.ts +++ b/packages/server/src/controllers/assistants/index.ts @@ -52,7 +52,14 @@ const deleteAssistant = async (req: Request, res: Response, next: NextFunction) `Error: assistantsController.deleteAssistant - id not provided!` ) } - const apiResponse = await assistantsService.deleteAssistant(req.params.id, req.query.isDeleteBoth) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: assistantsController.deleteAssistant - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await assistantsService.deleteAssistant(req.params.id, req.query.isDeleteBoth, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -62,7 +69,14 @@ const deleteAssistant = async (req: Request, res: Response, next: NextFunction) const getAllAssistants = async (req: Request, res: Response, next: NextFunction) => { try { const type = req.query.type as AssistantType - const apiResponse = await assistantsService.getAllAssistants(type, req.user?.activeWorkspaceId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: assistantsController.getAllAssistants - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await assistantsService.getAllAssistants(workspaceId, type) return res.json(apiResponse) } catch (error) { next(error) @@ -77,7 +91,14 @@ const getAssistantById = async (req: Request, res: Response, next: NextFunction) `Error: assistantsController.getAssistantById - id not provided!` ) } - const apiResponse = await assistantsService.getAssistantById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: assistantsController.getAssistantById - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await assistantsService.getAssistantById(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -98,7 +119,14 @@ const updateAssistant = async (req: Request, res: Response, next: NextFunction) `Error: assistantsController.updateAssistant - body not provided!` ) } - const apiResponse = await assistantsService.updateAssistant(req.params.id, req.body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: assistantsController.updateAssistant - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await assistantsService.updateAssistant(req.params.id, req.body, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -116,7 +144,14 @@ const getChatModels = async (req: Request, res: Response, next: NextFunction) => const getDocumentStores = async (req: Request, res: Response, next: NextFunction) => { try { - const apiResponse = await assistantsService.getDocumentStores(req.user?.activeWorkspaceId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: assistantsController.getDocumentStores - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await assistantsService.getDocumentStores(workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/export-import/index.ts b/packages/server/src/controllers/export-import/index.ts index b066df0c8a8..ae2a869283a 100644 --- a/packages/server/src/controllers/export-import/index.ts +++ b/packages/server/src/controllers/export-import/index.ts @@ -5,10 +5,14 @@ import exportImportService from '../../services/export-import' const exportData = async (req: Request, res: Response, next: NextFunction) => { try { - const apiResponse = await exportImportService.exportData( - exportImportService.convertExportInput(req.body), - req.user?.activeWorkspaceId - ) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: exportImportController.exportData - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await exportImportService.exportData(exportImportService.convertExportInput(req.body), workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/database/entities/ApiKey.ts b/packages/server/src/database/entities/ApiKey.ts index e7c1d84e55d..4778962a14f 100644 --- a/packages/server/src/database/entities/ApiKey.ts +++ b/packages/server/src/database/entities/ApiKey.ts @@ -19,6 +19,6 @@ export class ApiKey implements IApiKey { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Assistant.ts b/packages/server/src/database/entities/Assistant.ts index 28843213928..1d9eabbe8f1 100644 --- a/packages/server/src/database/entities/Assistant.ts +++ b/packages/server/src/database/entities/Assistant.ts @@ -27,6 +27,6 @@ export class Assistant implements IAssistant { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/ChatFlow.ts b/packages/server/src/database/entities/ChatFlow.ts index 4c14e99c1c4..1a49b9adac4 100644 --- a/packages/server/src/database/entities/ChatFlow.ts +++ b/packages/server/src/database/entities/ChatFlow.ts @@ -51,6 +51,6 @@ export class ChatFlow implements IChatFlow { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Credential.ts b/packages/server/src/database/entities/Credential.ts index 2c43158c487..5cff59f49ea 100644 --- a/packages/server/src/database/entities/Credential.ts +++ b/packages/server/src/database/entities/Credential.ts @@ -24,6 +24,6 @@ export class Credential implements ICredential { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/CustomTemplate.ts b/packages/server/src/database/entities/CustomTemplate.ts index e45719e69d4..ed99cebd3c6 100644 --- a/packages/server/src/database/entities/CustomTemplate.ts +++ b/packages/server/src/database/entities/CustomTemplate.ts @@ -27,7 +27,7 @@ export class CustomTemplate implements ICustomTemplate { @Column({ nullable: true, type: 'text' }) type?: string - @Column({ nullable: true, type: 'text' }) + @Column({ nullable: false, type: 'text' }) workspaceId: string @Column({ type: 'timestamp' }) diff --git a/packages/server/src/database/entities/Dataset.ts b/packages/server/src/database/entities/Dataset.ts index 8dd604d3d01..8acd5f2bb15 100644 --- a/packages/server/src/database/entities/Dataset.ts +++ b/packages/server/src/database/entities/Dataset.ts @@ -19,6 +19,6 @@ export class Dataset implements IDataset { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/DocumentStore.ts b/packages/server/src/database/entities/DocumentStore.ts index 01babca474b..9a94fde52fa 100644 --- a/packages/server/src/database/entities/DocumentStore.ts +++ b/packages/server/src/database/entities/DocumentStore.ts @@ -38,6 +38,6 @@ export class DocumentStore implements IDocumentStore { @Column({ nullable: true, type: 'text' }) recordManagerConfig: string | null - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Evaluation.ts b/packages/server/src/database/entities/Evaluation.ts index 85128ae0173..4f23a05e3c7 100644 --- a/packages/server/src/database/entities/Evaluation.ts +++ b/packages/server/src/database/entities/Evaluation.ts @@ -36,6 +36,6 @@ export class Evaluation implements IEvaluation { @UpdateDateColumn() runDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Evaluator.ts b/packages/server/src/database/entities/Evaluator.ts index 8e7f6f9b962..a14e0c9059a 100644 --- a/packages/server/src/database/entities/Evaluator.ts +++ b/packages/server/src/database/entities/Evaluator.ts @@ -23,6 +23,6 @@ export class Evaluator implements IEvaluator { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Execution.ts b/packages/server/src/database/entities/Execution.ts index 87885cf8a3f..eac53f6392a 100644 --- a/packages/server/src/database/entities/Execution.ts +++ b/packages/server/src/database/entities/Execution.ts @@ -42,6 +42,6 @@ export class Execution implements IExecution { @JoinColumn({ name: 'agentflowId' }) agentflow: ChatFlow - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Tool.ts b/packages/server/src/database/entities/Tool.ts index 3a0dcbc898a..2e35b64a685 100644 --- a/packages/server/src/database/entities/Tool.ts +++ b/packages/server/src/database/entities/Tool.ts @@ -33,6 +33,6 @@ export class Tool implements ITool { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/database/entities/Variable.ts b/packages/server/src/database/entities/Variable.ts index 6a8006dd67e..33105c0f6ed 100644 --- a/packages/server/src/database/entities/Variable.ts +++ b/packages/server/src/database/entities/Variable.ts @@ -24,6 +24,6 @@ export class Variable implements IVariable { @UpdateDateColumn() updatedDate: Date - @Column({ nullable: true, type: 'text' }) - workspaceId?: string + @Column({ nullable: false, type: 'text' }) + workspaceId: string } diff --git a/packages/server/src/services/assistants/index.ts b/packages/server/src/services/assistants/index.ts index 72dfc4a008a..0151124b9ca 100644 --- a/packages/server/src/services/assistants/index.ts +++ b/packages/server/src/services/assistants/index.ts @@ -160,11 +160,12 @@ const createAssistant = async (requestBody: any, orgId: string): Promise => { +const deleteAssistant = async (assistantId: string, isDeleteBoth: any, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({ - id: assistantId + id: assistantId, + workspaceId: workspaceId }) if (!assistant) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Assistant ${assistantId} not found`) @@ -225,7 +226,7 @@ async function getAssistantsCountByOrganization(type: AssistantType, organizatio } } -const getAllAssistants = async (type?: AssistantType, workspaceId?: string): Promise => { +const getAllAssistants = async (workspaceId: string, type?: AssistantType): Promise => { try { const appServer = getRunningExpressApp() if (type) { @@ -245,7 +246,7 @@ const getAllAssistants = async (type?: AssistantType, workspaceId?: string): Pro } } -const getAllAssistantsCount = async (type?: AssistantType, workspaceId?: string): Promise => { +const getAllAssistantsCount = async (workspaceId: string, type?: AssistantType): Promise => { try { const appServer = getRunningExpressApp() if (type) { @@ -265,11 +266,12 @@ const getAllAssistantsCount = async (type?: AssistantType, workspaceId?: string) } } -const getAssistantById = async (assistantId: string): Promise => { +const getAssistantById = async (assistantId: string, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() const dbResponse = await appServer.AppDataSource.getRepository(Assistant).findOneBy({ - id: assistantId + id: assistantId, + workspaceId: workspaceId }) if (!dbResponse) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Assistant ${assistantId} not found`) @@ -283,11 +285,12 @@ const getAssistantById = async (assistantId: string): Promise => { } } -const updateAssistant = async (assistantId: string, requestBody: any): Promise => { +const updateAssistant = async (assistantId: string, requestBody: any, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({ - id: assistantId + id: assistantId, + workspaceId: workspaceId }) if (!assistant) { @@ -461,7 +464,7 @@ const getChatModels = async (): Promise => { } } -const getDocumentStores = async (activeWorkspaceId?: string): Promise => { +const getDocumentStores = async (activeWorkspaceId: string): Promise => { try { const appServer = getRunningExpressApp() const stores = await appServer.AppDataSource.getRepository(DocumentStore).findBy(getWorkspaceSearchOptions(activeWorkspaceId)) diff --git a/packages/server/src/services/export-import/index.ts b/packages/server/src/services/export-import/index.ts index e2e39ead283..c27a0d382b8 100644 --- a/packages/server/src/services/export-import/index.ts +++ b/packages/server/src/services/export-import/index.ts @@ -88,7 +88,7 @@ const convertExportInput = (body: any): ExportInput => { } const FileDefaultName = 'ExportData.json' -const exportData = async (exportInput: ExportInput, activeWorkspaceId?: string): Promise<{ FileDefaultName: string } & ExportData> => { +const exportData = async (exportInput: ExportInput, activeWorkspaceId: string): Promise<{ FileDefaultName: string } & ExportData> => { try { let AgentFlow: ChatFlow[] | { data: ChatFlow[]; total: number } = exportInput.agentflow === true ? await chatflowService.getAllChatflows('MULTIAGENT', activeWorkspaceId) : [] @@ -99,17 +99,17 @@ const exportData = async (exportInput: ExportInput, activeWorkspaceId?: string): AgentFlowV2 = 'data' in AgentFlowV2 ? AgentFlowV2.data : AgentFlowV2 let AssistantCustom: Assistant[] = - exportInput.assistantCustom === true ? await assistantService.getAllAssistants('CUSTOM', activeWorkspaceId) : [] + exportInput.assistantCustom === true ? await assistantService.getAllAssistants(activeWorkspaceId, 'CUSTOM') : [] let AssistantFlow: ChatFlow[] | { data: ChatFlow[]; total: number } = exportInput.assistantCustom === true ? await chatflowService.getAllChatflows('ASSISTANT', activeWorkspaceId) : [] AssistantFlow = 'data' in AssistantFlow ? AssistantFlow.data : AssistantFlow let AssistantOpenAI: Assistant[] = - exportInput.assistantOpenAI === true ? await assistantService.getAllAssistants('OPENAI', activeWorkspaceId) : [] + exportInput.assistantOpenAI === true ? await assistantService.getAllAssistants(activeWorkspaceId, 'OPENAI') : [] let AssistantAzure: Assistant[] = - exportInput.assistantAzure === true ? await assistantService.getAllAssistants('AZURE', activeWorkspaceId) : [] + exportInput.assistantAzure === true ? await assistantService.getAllAssistants(activeWorkspaceId, 'AZURE') : [] let ChatFlow: ChatFlow[] | { data: ChatFlow[]; total: number } = exportInput.chatflow === true ? await chatflowService.getAllChatflows('CHATFLOW', activeWorkspaceId) : [] diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 89aa4e3b742..78cc2e0ea05 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -833,7 +833,8 @@ export const getGlobalVariable = async ( value: overrideConfig.vars[propertyName], id: '', updatedDate: new Date(), - createdDate: new Date() + createdDate: new Date(), + workspaceId: 'dummy' }) } } From 29a2f7415031186bac6be612ffa957daaf8974df Mon Sep 17 00:00:00 2001 From: Vinod Kiran Date: Wed, 30 Jul 2025 15:26:12 +0530 Subject: [PATCH 03/10] Require workspace ID in various controllers and services - Updated controllers for credentials, datasets, document stores, evaluations, evaluators, and variables to enforce the presence of `workspaceId`. - Enhanced error handling to throw appropriate errors when `workspaceId` is not provided. - Modified service methods to accept `workspaceId` as a mandatory parameter for operations, ensuring consistent validation across the application. --- .../src/controllers/credentials/index.ts | 36 ++++- .../server/src/controllers/dataset/index.ts | 89 ++++++++++-- .../src/controllers/documentstore/index.ts | 100 ++++++++++++-- .../src/controllers/evaluations/index.ts | 65 +++++++-- .../src/controllers/evaluators/index.ts | 36 ++++- .../server/src/controllers/variables/index.ts | 27 +++- .../server/src/services/credentials/index.ts | 14 +- packages/server/src/services/dataset/index.ts | 39 +++--- .../src/services/documentstore/index.ts | 127 +++++++++++------- .../server/src/services/evaluations/index.ts | 50 ++++--- .../server/src/services/evaluator/index.ts | 18 +-- .../server/src/services/variables/index.ts | 11 +- 12 files changed, 469 insertions(+), 143 deletions(-) diff --git a/packages/server/src/controllers/credentials/index.ts b/packages/server/src/controllers/credentials/index.ts index 78cb4348c5e..6c6dea22a34 100644 --- a/packages/server/src/controllers/credentials/index.ts +++ b/packages/server/src/controllers/credentials/index.ts @@ -28,7 +28,14 @@ const deleteCredentials = async (req: Request, res: Response, next: NextFunction `Error: credentialsController.deleteCredentials - id not provided!` ) } - const apiResponse = await credentialsService.deleteCredentials(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: credentialsController.deleteCredentials - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await credentialsService.deleteCredentials(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -37,7 +44,14 @@ const deleteCredentials = async (req: Request, res: Response, next: NextFunction const getAllCredentials = async (req: Request, res: Response, next: NextFunction) => { try { - const apiResponse = await credentialsService.getAllCredentials(req.query.credentialName, req.user?.activeWorkspaceId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: credentialsController.getAllCredentials - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await credentialsService.getAllCredentials(req.query.credentialName, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -52,7 +66,14 @@ const getCredentialById = async (req: Request, res: Response, next: NextFunction `Error: credentialsController.getCredentialById - id not provided!` ) } - const apiResponse = await credentialsService.getCredentialById(req.params.id, req.user?.activeWorkspaceId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: credentialsController.getCredentialById - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await credentialsService.getCredentialById(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -73,7 +94,14 @@ const updateCredential = async (req: Request, res: Response, next: NextFunction) `Error: credentialsController.updateCredential - body not provided!` ) } - const apiResponse = await credentialsService.updateCredential(req.params.id, req.body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: credentialsController.updateCredential - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await credentialsService.updateCredential(req.params.id, req.body, workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/dataset/index.ts b/packages/server/src/controllers/dataset/index.ts index d6c7531181f..0479a20de0d 100644 --- a/packages/server/src/controllers/dataset/index.ts +++ b/packages/server/src/controllers/dataset/index.ts @@ -7,7 +7,14 @@ import { getPageAndLimitParams } from '../../utils/pagination' const getAllDatasets = async (req: Request, res: Response, next: NextFunction) => { try { const { page, limit } = getPageAndLimitParams(req) - const apiResponse = await datasetService.getAllDatasets(req.user?.activeWorkspaceId, page, limit) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.getAllDatasets - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.getAllDatasets(workspaceId, page, limit) return res.json(apiResponse) } catch (error) { next(error) @@ -20,7 +27,14 @@ const getDataset = async (req: Request, res: Response, next: NextFunction) => { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.getDataset - id not provided!`) } const { page, limit } = getPageAndLimitParams(req) - const apiResponse = await datasetService.getDataset(req.params.id, page, limit) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.getDataset - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.getDataset(req.params.id, workspaceId, page, limit) return res.json(apiResponse) } catch (error) { next(error) @@ -33,7 +47,14 @@ const createDataset = async (req: Request, res: Response, next: NextFunction) => throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.createDataset - body not provided!`) } const body = req.body - body.workspaceId = req.user?.activeWorkspaceId + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.createDataset - workspace ${workspaceId} not found!` + ) + } + body.workspaceId = workspaceId const apiResponse = await datasetService.createDataset(body) return res.json(apiResponse) } catch (error) { @@ -49,7 +70,14 @@ const updateDataset = async (req: Request, res: Response, next: NextFunction) => if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.updateDataset - id not provided!`) } - const apiResponse = await datasetService.updateDataset(req.params.id, req.body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.updateDataset - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.updateDataset(req.params.id, req.body, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -61,7 +89,14 @@ const deleteDataset = async (req: Request, res: Response, next: NextFunction) => if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.deleteDataset - id not provided!`) } - const apiResponse = await datasetService.deleteDataset(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.deleteDataset - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.deleteDataset(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -76,6 +111,14 @@ const addDatasetRow = async (req: Request, res: Response, next: NextFunction) => if (!req.body.datasetId) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.addDatasetRow - datasetId not provided!`) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.addDatasetRow - workspace ${workspaceId} not found!` + ) + } + req.body.workspaceId = workspaceId const apiResponse = await datasetService.addDatasetRow(req.body) return res.json(apiResponse) } catch (error) { @@ -91,6 +134,14 @@ const updateDatasetRow = async (req: Request, res: Response, next: NextFunction) if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.updateDatasetRow - id not provided!`) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.updateDatasetRow - workspace ${workspaceId} not found!` + ) + } + req.body.workspaceId = workspaceId const apiResponse = await datasetService.updateDatasetRow(req.params.id, req.body) return res.json(apiResponse) } catch (error) { @@ -103,7 +154,14 @@ const deleteDatasetRow = async (req: Request, res: Response, next: NextFunction) if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.deleteDatasetRow - id not provided!`) } - const apiResponse = await datasetService.deleteDatasetRow(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.deleteDatasetRow - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.deleteDatasetRow(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -113,7 +171,14 @@ const deleteDatasetRow = async (req: Request, res: Response, next: NextFunction) const patchDeleteRows = async (req: Request, res: Response, next: NextFunction) => { try { const ids = req.body.ids ?? [] - const apiResponse = await datasetService.patchDeleteRows(ids) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.patchDeleteRows - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.patchDeleteRows(ids, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -125,8 +190,14 @@ const reorderDatasetRow = async (req: Request, res: Response, next: NextFunction if (!req.body) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.reorderDatasetRow - body not provided!`) } - - const apiResponse = await datasetService.reorderDatasetRow(req.body.datasetId, req.body.rows) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: datasetController.reorderDatasetRow - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await datasetService.reorderDatasetRow(req.body.datasetId, req.body.rows, workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/documentstore/index.ts b/packages/server/src/controllers/documentstore/index.ts index 9d19373b436..1ac4f439575 100644 --- a/packages/server/src/controllers/documentstore/index.ts +++ b/packages/server/src/controllers/documentstore/index.ts @@ -27,7 +27,12 @@ const createDocumentStore = async (req: Request, res: Response, next: NextFuncti const body = req.body body.workspaceId = req.user?.activeWorkspaceId - + if (!body.workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.createDocumentStore - workspaceId not provided!` + ) + } const docStore = DocumentStoreDTO.toEntity(body) const apiResponse = await documentStoreService.createDocumentStore(docStore, orgId) return res.json(apiResponse) @@ -40,7 +45,14 @@ const getAllDocumentStores = async (req: Request, res: Response, next: NextFunct try { const { page, limit } = getPageAndLimitParams(req) - const apiResponse: any = await documentStoreService.getAllDocumentStores(req.user?.activeWorkspaceId, page, limit) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.getAllDocumentStores - workspaceId not provided!` + ) + } + const apiResponse: any = await documentStoreService.getAllDocumentStores(workspaceId, page, limit) if (apiResponse?.total >= 0) { return res.json({ total: apiResponse.total, @@ -102,9 +114,16 @@ const getDocumentStoreById = async (req: Request, res: Response, next: NextFunct `Error: documentStoreController.getDocumentStoreById - id not provided!` ) } - const apiResponse = await documentStoreService.getDocumentStoreById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.getDocumentStoreById - workspaceId not provided!` + ) + } + const apiResponse = await documentStoreService.getDocumentStoreById(req.params.id, workspaceId) if (apiResponse && apiResponse.whereUsed) { - apiResponse.whereUsed = JSON.stringify(await documentStoreService.getUsedChatflowNames(apiResponse)) + apiResponse.whereUsed = JSON.stringify(await documentStoreService.getUsedChatflowNames(apiResponse, workspaceId)) } return res.json(DocumentStoreDTO.fromEntity(apiResponse)) } catch (error) { @@ -126,12 +145,20 @@ const getDocumentStoreFileChunks = async (req: Request, res: Response, next: Nex `Error: documentStoreController.getDocumentStoreFileChunks - fileId not provided!` ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.getDocumentStoreFileChunks - workspaceId not provided!` + ) + } const appDataSource = getRunningExpressApp().AppDataSource const page = req.params.pageNo ? parseInt(req.params.pageNo) : 1 const apiResponse = await documentStoreService.getDocumentStoreFileChunks( appDataSource, req.params.storeId, req.params.fileId, + workspaceId, page ) return res.json(apiResponse) @@ -160,10 +187,18 @@ const deleteDocumentStoreFileChunk = async (req: Request, res: Response, next: N `Error: documentStoreController.deleteDocumentStoreFileChunk - chunkId not provided!` ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.deleteDocumentStoreFileChunk - workspaceId not provided!` + ) + } const apiResponse = await documentStoreService.deleteDocumentStoreFileChunk( req.params.storeId, req.params.loaderId, - req.params.chunkId + req.params.chunkId, + workspaceId ) return res.json(apiResponse) } catch (error) { @@ -198,12 +233,20 @@ const editDocumentStoreFileChunk = async (req: Request, res: Response, next: Nex `Error: documentStoreController.editDocumentStoreFileChunk - body not provided!` ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.editDocumentStoreFileChunk - workspaceId not provided!` + ) + } const apiResponse = await documentStoreService.editDocumentStoreFileChunk( req.params.storeId, req.params.loaderId, req.params.chunkId, body.pageContent, - body.metadata + body.metadata, + workspaceId ) return res.json(apiResponse) } catch (error) { @@ -221,7 +264,14 @@ const saveProcessingLoader = async (req: Request, res: Response, next: NextFunct ) } const body = req.body - const apiResponse = await documentStoreService.saveProcessingLoader(appServer.AppDataSource, body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.saveProcessingLoader - workspaceId not provided!` + ) + } + const apiResponse = await documentStoreService.saveProcessingLoader(appServer.AppDataSource, body, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -289,7 +339,14 @@ const updateDocumentStore = async (req: Request, res: Response, next: NextFuncti `Error: documentStoreController.updateDocumentStore - body not provided!` ) } - const store = await documentStoreService.getDocumentStoreById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.updateDocumentStore - workspaceId not provided!` + ) + } + const store = await documentStoreService.getDocumentStoreById(req.params.id, workspaceId) if (!store) { throw new InternalFlowiseError( StatusCodes.NOT_FOUND, @@ -449,7 +506,14 @@ const deleteVectorStoreFromStore = async (req: Request, res: Response, next: Nex `Error: documentStoreController.deleteVectorStoreFromStore - storeId not provided!` ) } - const apiResponse = await documentStoreService.deleteVectorStoreFromStore(req.params.storeId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.deleteVectorStoreFromStore - workspaceId not provided!` + ) + } + const apiResponse = await documentStoreService.deleteVectorStoreFromStore(req.params.storeId, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -463,7 +527,14 @@ const saveVectorStoreConfig = async (req: Request, res: Response, next: NextFunc } const body = req.body const appDataSource = getRunningExpressApp().AppDataSource - const apiResponse = await documentStoreService.saveVectorStoreConfig(appDataSource, body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.saveVectorStoreConfig - workspaceId not provided!` + ) + } + const apiResponse = await documentStoreService.saveVectorStoreConfig(appDataSource, body, true, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -476,7 +547,14 @@ const updateVectorStoreConfigOnly = async (req: Request, res: Response, next: Ne throw new Error('Error: documentStoreController.updateVectorStoreConfigOnly - body not provided!') } const body = req.body - const apiResponse = await documentStoreService.updateVectorStoreConfigOnly(body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + `Error: documentStoreController.updateVectorStoreConfigOnly - workspaceId not provided!` + ) + } + const apiResponse = await documentStoreService.updateVectorStoreConfigOnly(body, workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/evaluations/index.ts b/packages/server/src/controllers/evaluations/index.ts index 4cde7615804..b38213aa0b7 100644 --- a/packages/server/src/controllers/evaluations/index.ts +++ b/packages/server/src/controllers/evaluations/index.ts @@ -31,7 +31,7 @@ const createEvaluation = async (req: Request, res: Response, next: NextFunction) const httpProtocol = req.get('x-forwarded-proto') || req.get('X-Forwarded-Proto') || req.protocol const baseURL = `${httpProtocol}://${req.get('host')}` - const apiResponse = await evaluationsService.createEvaluation(body, baseURL, orgId) + const apiResponse = await evaluationsService.createEvaluation(body, baseURL, orgId, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -47,9 +47,16 @@ const runAgain = async (req: Request, res: Response, next: NextFunction) => { if (!orgId) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Error: evaluationsService.runAgain - organization ${orgId} not found!`) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.runAgain - workspace ${workspaceId} not found!` + ) + } const httpProtocol = req.get('x-forwarded-proto') || req.get('X-Forwarded-Proto') || req.protocol const baseURL = `${httpProtocol}://${req.get('host')}` - const apiResponse = await evaluationsService.runAgain(req.params.id, baseURL, orgId) + const apiResponse = await evaluationsService.runAgain(req.params.id, baseURL, orgId, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -61,7 +68,14 @@ const getEvaluation = async (req: Request, res: Response, next: NextFunction) => if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.getEvaluation - id not provided!`) } - const apiResponse = await evaluationsService.getEvaluation(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.getEvaluation - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluationsService.getEvaluation(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -73,7 +87,14 @@ const deleteEvaluation = async (req: Request, res: Response, next: NextFunction) if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.deleteEvaluation - id not provided!`) } - const apiResponse = await evaluationsService.deleteEvaluation(req.params.id, req.user?.activeWorkspaceId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.deleteEvaluation - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluationsService.deleteEvaluation(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -83,7 +104,14 @@ const deleteEvaluation = async (req: Request, res: Response, next: NextFunction) const getAllEvaluations = async (req: Request, res: Response, next: NextFunction) => { try { const { page, limit } = getPageAndLimitParams(req) - const apiResponse = await evaluationsService.getAllEvaluations(req.user?.activeWorkspaceId, page, limit) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.getAllEvaluations - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluationsService.getAllEvaluations(workspaceId, page, limit) return res.json(apiResponse) } catch (error) { next(error) @@ -95,7 +123,14 @@ const isOutdated = async (req: Request, res: Response, next: NextFunction) => { if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.isOutdated - id not provided!`) } - const apiResponse = await evaluationsService.isOutdated(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.isOutdated - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluationsService.isOutdated(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -107,7 +142,14 @@ const getVersions = async (req: Request, res: Response, next: NextFunction) => { if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.getVersions - id not provided!`) } - const apiResponse = await evaluationsService.getVersions(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.getVersions - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluationsService.getVersions(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -118,7 +160,14 @@ const patchDeleteEvaluations = async (req: Request, res: Response, next: NextFun try { const ids = req.body.ids ?? [] const isDeleteAllVersion = req.body.isDeleteAllVersion ?? false - const apiResponse = await evaluationsService.patchDeleteEvaluations(ids, isDeleteAllVersion, req.user?.activeWorkspaceId) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluationsService.patchDeleteEvaluations - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluationsService.patchDeleteEvaluations(ids, workspaceId, isDeleteAllVersion) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/evaluators/index.ts b/packages/server/src/controllers/evaluators/index.ts index 1f151c37c73..5f864a85c5e 100644 --- a/packages/server/src/controllers/evaluators/index.ts +++ b/packages/server/src/controllers/evaluators/index.ts @@ -7,7 +7,14 @@ import { getPageAndLimitParams } from '../../utils/pagination' const getAllEvaluators = async (req: Request, res: Response, next: NextFunction) => { try { const { page, limit } = getPageAndLimitParams(req) - const apiResponse = await evaluatorService.getAllEvaluators(req.user?.activeWorkspaceId, page, limit) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluatorService.getAllEvaluators - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluatorService.getAllEvaluators(workspaceId, page, limit) return res.json(apiResponse) } catch (error) { next(error) @@ -19,7 +26,14 @@ const getEvaluator = async (req: Request, res: Response, next: NextFunction) => if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.getEvaluator - id not provided!`) } - const apiResponse = await evaluatorService.getEvaluator(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluatorService.getEvaluator - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluatorService.getEvaluator(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -48,7 +62,14 @@ const updateEvaluator = async (req: Request, res: Response, next: NextFunction) if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.updateEvaluator - id not provided!`) } - const apiResponse = await evaluatorService.updateEvaluator(req.params.id, req.body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluatorService.updateEvaluator - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluatorService.updateEvaluator(req.params.id, req.body, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -60,7 +81,14 @@ const deleteEvaluator = async (req: Request, res: Response, next: NextFunction) if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.deleteEvaluator - id not provided!`) } - const apiResponse = await evaluatorService.deleteEvaluator(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: evaluatorService.deleteEvaluator - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await evaluatorService.deleteEvaluator(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/variables/index.ts b/packages/server/src/controllers/variables/index.ts index 42b58603ecc..3f8455410bf 100644 --- a/packages/server/src/controllers/variables/index.ts +++ b/packages/server/src/controllers/variables/index.ts @@ -37,7 +37,14 @@ const deleteVariable = async (req: Request, res: Response, next: NextFunction) = if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'Error: variablesController.deleteVariable - id not provided!') } - const apiResponse = await variablesService.deleteVariable(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: variablesController.deleteVariable - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await variablesService.deleteVariable(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -47,7 +54,14 @@ const deleteVariable = async (req: Request, res: Response, next: NextFunction) = const getAllVariables = async (req: Request, res: Response, next: NextFunction) => { try { const { page, limit } = getPageAndLimitParams(req) - const apiResponse = await variablesService.getAllVariables(req.user?.activeWorkspaceId, page, limit) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: variablesController.getAllVariables - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await variablesService.getAllVariables(workspaceId, page, limit) return res.json(apiResponse) } catch (error) { next(error) @@ -65,7 +79,14 @@ const updateVariable = async (req: Request, res: Response, next: NextFunction) = 'Error: variablesController.updateVariable - body not provided!' ) } - const variable = await variablesService.getVariableById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: variablesController.updateVariable - workspace ${workspaceId} not found!` + ) + } + const variable = await variablesService.getVariableById(req.params.id, workspaceId) if (!variable) { return res.status(404).send(`Variable ${req.params.id} not found in the database`) } diff --git a/packages/server/src/services/credentials/index.ts b/packages/server/src/services/credentials/index.ts index 89219cabe26..e07c32da3af 100644 --- a/packages/server/src/services/credentials/index.ts +++ b/packages/server/src/services/credentials/index.ts @@ -31,10 +31,10 @@ const createCredential = async (requestBody: any) => { } // Delete all credentials from chatflowid -const deleteCredentials = async (credentialId: string): Promise => { +const deleteCredentials = async (credentialId: string, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() - const dbResponse = await appServer.AppDataSource.getRepository(Credential).delete({ id: credentialId }) + const dbResponse = await appServer.AppDataSource.getRepository(Credential).delete({ id: credentialId, workspaceId: workspaceId }) if (!dbResponse) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found`) } @@ -47,7 +47,7 @@ const deleteCredentials = async (credentialId: string): Promise => { } } -const getAllCredentials = async (paramCredentialName: any, workspaceId?: string) => { +const getAllCredentials = async (paramCredentialName: any, workspaceId: string) => { try { const appServer = getRunningExpressApp() let dbResponse = [] @@ -124,7 +124,7 @@ const getAllCredentials = async (paramCredentialName: any, workspaceId?: string) } } -const getCredentialById = async (credentialId: string, workspaceId?: string): Promise => { +const getCredentialById = async (credentialId: string, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ @@ -165,11 +165,12 @@ const getCredentialById = async (credentialId: string, workspaceId?: string): Pr } } -const updateCredential = async (credentialId: string, requestBody: any): Promise => { +const updateCredential = async (credentialId: string, requestBody: any, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId + id: credentialId, + workspaceId: workspaceId }) if (!credential) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found`) @@ -177,6 +178,7 @@ const updateCredential = async (credentialId: string, requestBody: any): Promise const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) requestBody.plainDataObj = { ...decryptedCredentialData, ...requestBody.plainDataObj } const updateCredential = await transformToCredentialEntity(requestBody) + updateCredential.workspaceId = workspaceId await appServer.AppDataSource.getRepository(Credential).merge(credential, updateCredential) const dbResponse = await appServer.AppDataSource.getRepository(Credential).save(credential) return dbResponse diff --git a/packages/server/src/services/dataset/index.ts b/packages/server/src/services/dataset/index.ts index 351919e6b23..4042e420f40 100644 --- a/packages/server/src/services/dataset/index.ts +++ b/packages/server/src/services/dataset/index.ts @@ -9,7 +9,7 @@ import { In } from 'typeorm' import csv from 'csv-parser' -const getAllDatasets = async (workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllDatasets = async (workspaceId: string, page: number = -1, limit: number = -1) => { try { const appServer = getRunningExpressApp() const queryBuilder = appServer.AppDataSource.getRepository(Dataset).createQueryBuilder('ds').orderBy('ds.updatedDate', 'DESC') @@ -43,11 +43,12 @@ const getAllDatasets = async (workspaceId?: string, page: number = -1, limit: nu } } -const getDataset = async (id: string, page: number = -1, limit: number = -1) => { +const getDataset = async (id: string, workspaceId: string, page: number = -1, limit: number = -1) => { try { const appServer = getRunningExpressApp() const dataset = await appServer.AppDataSource.getRepository(Dataset).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) const queryBuilder = appServer.AppDataSource.getRepository(DatasetRow).createQueryBuilder('dsr').orderBy('dsr.sequenceNo', 'ASC') queryBuilder.andWhere('dsr.datasetId = :datasetId', { datasetId: id }) @@ -88,7 +89,7 @@ const getDataset = async (id: string, page: number = -1, limit: number = -1) => } } -const reorderDatasetRow = async (datasetId: string, rows: any[]) => { +const reorderDatasetRow = async (datasetId: string, rows: any[], workspaceId: string) => { try { const appServer = getRunningExpressApp() await appServer.AppDataSource.transaction(async (entityManager) => { @@ -102,7 +103,7 @@ const reorderDatasetRow = async (datasetId: string, rows: any[]) => { item.sequenceNo = row.sequenceNo await entityManager.getRepository(DatasetRow).save(item) } - await changeUpdateOnDataset(datasetId, entityManager) + await changeUpdateOnDataset(datasetId, workspaceId, entityManager) }) return { message: 'Dataset row reordered successfully' } } catch (error) { @@ -211,11 +212,12 @@ const createDataset = async (body: any) => { } // Update dataset -const updateDataset = async (id: string, body: any) => { +const updateDataset = async (id: string, body: any, workspaceId: string) => { try { const appServer = getRunningExpressApp() const dataset = await appServer.AppDataSource.getRepository(Dataset).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!dataset) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Dataset ${id} not found`) @@ -230,10 +232,10 @@ const updateDataset = async (id: string, body: any) => { } // Delete dataset via id -const deleteDataset = async (id: string) => { +const deleteDataset = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() - const result = await appServer.AppDataSource.getRepository(Dataset).delete({ id: id }) + const result = await appServer.AppDataSource.getRepository(Dataset).delete({ id: id, workspaceId: workspaceId }) // delete all rows for this dataset await appServer.AppDataSource.getRepository(DatasetRow).delete({ datasetId: id }) @@ -250,7 +252,7 @@ const addDatasetRow = async (body: any) => { const appServer = getRunningExpressApp() if (body.csvFile) { await _csvToDatasetRows(body.datasetId, body.csvFile, body.firstRowHeaders) - await changeUpdateOnDataset(body.datasetId) + await changeUpdateOnDataset(body.datasetId, body.workspaceId) return { message: 'Dataset rows added successfully' } } else { // get the max value first @@ -272,7 +274,7 @@ const addDatasetRow = async (body: any) => { newDs.sequenceNo = sequenceNo === 0 ? sequenceNo : sequenceNo + 1 const row = appServer.AppDataSource.getRepository(DatasetRow).create(newDs) const result = await appServer.AppDataSource.getRepository(DatasetRow).save(row) - await changeUpdateOnDataset(body.datasetId) + await changeUpdateOnDataset(body.datasetId, body.workspaceId) return result } } catch (error) { @@ -283,10 +285,11 @@ const addDatasetRow = async (body: any) => { } } -const changeUpdateOnDataset = async (id: string, entityManager?: any) => { +const changeUpdateOnDataset = async (id: string, workspaceId: string, entityManager?: any) => { const appServer = getRunningExpressApp() const dataset = await appServer.AppDataSource.getRepository(Dataset).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!dataset) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Dataset ${id} not found`) @@ -311,7 +314,7 @@ const updateDatasetRow = async (id: string, body: any) => { Object.assign(updateItem, body) appServer.AppDataSource.getRepository(DatasetRow).merge(item, updateItem) const result = await appServer.AppDataSource.getRepository(DatasetRow).save(item) - await changeUpdateOnDataset(body.datasetId) + await changeUpdateOnDataset(body.datasetId, body.workspaceId) return result } catch (error) { throw new InternalFlowiseError( @@ -322,7 +325,7 @@ const updateDatasetRow = async (id: string, body: any) => { } // Delete dataset row via id -const deleteDatasetRow = async (id: string) => { +const deleteDatasetRow = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() return await appServer.AppDataSource.transaction(async (entityManager) => { @@ -332,7 +335,7 @@ const deleteDatasetRow = async (id: string) => { if (!item) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Dataset Row ${id} not found`) const result = await entityManager.getRepository(DatasetRow).delete({ id: id }) - await changeUpdateOnDataset(item.datasetId, entityManager) + await changeUpdateOnDataset(item.datasetId, workspaceId, entityManager) return result }) } catch (error) { @@ -344,7 +347,7 @@ const deleteDatasetRow = async (id: string) => { } // Delete dataset rows via ids -const patchDeleteRows = async (ids: string[] = []) => { +const patchDeleteRows = async (ids: string[] = [], workspaceId: string) => { try { const appServer = getRunningExpressApp() const datasetItemsToBeDeleted = await appServer.AppDataSource.getRepository(DatasetRow).find({ @@ -356,7 +359,7 @@ const patchDeleteRows = async (ids: string[] = []) => { const datasetIds = [...new Set(datasetItemsToBeDeleted.map((item) => item.datasetId))] for (const datasetId of datasetIds) { - await changeUpdateOnDataset(datasetId) + await changeUpdateOnDataset(datasetId, workspaceId) } return dbResponse } catch (error) { diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts index a9a12fd47ce..ccbaf6b351e 100644 --- a/packages/server/src/services/documentstore/index.ts +++ b/packages/server/src/services/documentstore/index.ts @@ -77,7 +77,7 @@ const createDocumentStore = async (newDocumentStore: DocumentStore, orgId: strin } } -const getAllDocumentStores = async (workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllDocumentStores = async (workspaceId: string, page: number = -1, limit: number = -1) => { try { const appServer = getRunningExpressApp() const queryBuilder = appServer.AppDataSource.getRepository(DocumentStore) @@ -88,7 +88,7 @@ const getAllDocumentStores = async (workspaceId?: string, page: number = -1, lim queryBuilder.skip((page - 1) * limit) queryBuilder.take(limit) } - if (workspaceId) queryBuilder.andWhere('doc_store.workspaceId = :workspaceId', { workspaceId }) + queryBuilder.andWhere('doc_store.workspaceId = :workspaceId', { workspaceId }) const [data, total] = await queryBuilder.getManyAndCount() @@ -172,11 +172,12 @@ const deleteLoaderFromDocumentStore = async ( } } -const getDocumentStoreById = async (storeId: string) => { +const getDocumentStoreById = async (storeId: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ - id: storeId + id: storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError( @@ -193,7 +194,7 @@ const getDocumentStoreById = async (storeId: string) => { } } -const getUsedChatflowNames = async (entity: DocumentStore) => { +const getUsedChatflowNames = async (entity: DocumentStore, workspaceId: string) => { try { const appServer = getRunningExpressApp() if (entity.whereUsed) { @@ -201,7 +202,7 @@ const getUsedChatflowNames = async (entity: DocumentStore) => { const updatedWhereUsed: IDocumentStoreWhereUsed[] = [] for (let i = 0; i < whereUsed.length; i++) { const associatedChatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOne({ - where: { id: whereUsed[i] }, + where: { id: whereUsed[i], workspaceId: workspaceId }, select: ['id', 'name'] }) if (associatedChatflow) { @@ -223,10 +224,17 @@ const getUsedChatflowNames = async (entity: DocumentStore) => { } // Get chunks for a specific loader or store -const getDocumentStoreFileChunks = async (appDataSource: DataSource, storeId: string, docId: string, pageNo: number = 1) => { +const getDocumentStoreFileChunks = async ( + appDataSource: DataSource, + storeId: string, + docId: string, + workspaceId: string, + pageNo: number = 1 +) => { try { const entity = await appDataSource.getRepository(DocumentStore).findOneBy({ - id: storeId + id: storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError( @@ -303,28 +311,30 @@ const getDocumentStoreFileChunks = async (appDataSource: DataSource, storeId: st } } -const deleteDocumentStore = async (storeId: string, orgId: string, workspaceId: string, usageCacheManager: UsageCacheManager) => { +const deleteDocumentStore = async ( + storeId: string, + orgId: string, + workspaceId: string, + usageCacheManager: UsageCacheManager, + isDeleteBoth: boolean = false +) => { try { const appServer = getRunningExpressApp() - // delete all the chunks associated with the store - await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).delete({ - storeId: storeId - }) - // now delete the files associated with the store const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ - id: storeId + id: storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${storeId} not found`) } - if (workspaceId) { - if (entity?.workspaceId !== workspaceId) { - throw new Error('Unauthorized access') - } - } + // delete all the chunks associated with the store + await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).delete({ + storeId: storeId + }) + // now delete the files associated with the store try { const { totalSize } = await removeFilesFromStorage(orgId, DOCUMENT_STORE_BASE_FOLDER, entity.id) await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager) @@ -351,11 +361,12 @@ const deleteDocumentStore = async (storeId: string, orgId: string, workspaceId: } } -const deleteDocumentStoreFileChunk = async (storeId: string, docId: string, chunkId: string) => { +const deleteDocumentStoreFileChunk = async (storeId: string, docId: string, chunkId: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ - id: storeId + id: storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${storeId} not found`) @@ -377,7 +388,7 @@ const deleteDocumentStoreFileChunk = async (storeId: string, docId: string, chun found.totalChars -= tbdChunk.pageContent.length entity.loaders = JSON.stringify(loaders) await appServer.AppDataSource.getRepository(DocumentStore).save(entity) - return getDocumentStoreFileChunks(appServer.AppDataSource, storeId, docId) + return getDocumentStoreFileChunks(appServer.AppDataSource, storeId, docId, workspaceId) } catch (error) { throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, @@ -386,13 +397,14 @@ const deleteDocumentStoreFileChunk = async (storeId: string, docId: string, chun } } -const deleteVectorStoreFromStore = async (storeId: string) => { +const deleteVectorStoreFromStore = async (storeId: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const componentNodes = appServer.nodesPool.componentNodes const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ - id: storeId + id: storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${storeId} not found`) @@ -465,11 +477,19 @@ const deleteVectorStoreFromStore = async (storeId: string) => { } } -const editDocumentStoreFileChunk = async (storeId: string, docId: string, chunkId: string, content: string, metadata: ICommonObject) => { +const editDocumentStoreFileChunk = async ( + storeId: string, + docId: string, + chunkId: string, + content: string, + metadata: ICommonObject, + workspaceId: string +) => { try { const appServer = getRunningExpressApp() const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ - id: storeId + id: storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${storeId} not found`) @@ -493,7 +513,7 @@ const editDocumentStoreFileChunk = async (storeId: string, docId: string, chunkI await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).save(editChunk) entity.loaders = JSON.stringify(loaders) await appServer.AppDataSource.getRepository(DocumentStore).save(entity) - return getDocumentStoreFileChunks(appServer.AppDataSource, storeId, docId) + return getDocumentStoreFileChunks(appServer.AppDataSource, storeId, docId, workspaceId) } catch (error) { throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, @@ -717,10 +737,15 @@ export const previewChunks = async ({ appDataSource, componentNodes, data, orgId } } -const saveProcessingLoader = async (appDataSource: DataSource, data: IDocumentStoreLoaderForPreview): Promise => { +const saveProcessingLoader = async ( + appDataSource: DataSource, + data: IDocumentStoreLoaderForPreview, + workspaceId: string +): Promise => { try { const entity = await appDataSource.getRepository(DocumentStore).findOneBy({ - id: data.storeId + id: data.storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError( @@ -808,7 +833,8 @@ export const processLoader = async ({ usageCacheManager }: IExecuteProcessLoader) => { const entity = await appDataSource.getRepository(DocumentStore).findOneBy({ - id: data.storeId + id: data.storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError( @@ -816,11 +842,6 @@ export const processLoader = async ({ `Error: documentStoreServices.processLoader - Document store ${data.storeId} not found` ) } - if (workspaceId) { - if (entity?.workspaceId !== workspaceId) { - throw new Error('Unauthorized access') - } - } await _saveChunksToStorage( appDataSource, componentNodes, @@ -832,7 +853,7 @@ export const processLoader = async ({ subscriptionId, usageCacheManager ) - return getDocumentStoreFileChunks(appDataSource, data.storeId as string, docLoaderId) + return getDocumentStoreFileChunks(appDataSource, data.storeId as string, docLoaderId, workspaceId) } const processLoaderMiddleware = async ( @@ -1113,11 +1134,12 @@ const updateDocumentStoreUsage = async (chatId: string, storeId: string | undefi } } -const updateVectorStoreConfigOnly = async (data: ICommonObject) => { +const updateVectorStoreConfigOnly = async (data: ICommonObject, workspaceId: string) => { try { const appServer = getRunningExpressApp() const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ - id: data.storeId + id: data.storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${data.storeId} not found`) @@ -1140,10 +1162,11 @@ const updateVectorStoreConfigOnly = async (data: ICommonObject) => { ) } } -const saveVectorStoreConfig = async (appDataSource: DataSource, data: ICommonObject, isStrictSave = true) => { +const saveVectorStoreConfig = async (appDataSource: DataSource, data: ICommonObject, isStrictSave = true, workspaceId: string) => { try { const entity = await appDataSource.getRepository(DocumentStore).findOneBy({ - id: data.storeId + id: data.storeId, + workspaceId: workspaceId }) if (!entity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${data.storeId} not found`) @@ -1209,14 +1232,23 @@ export const insertIntoVectorStore = async ({ telemetry, data, isStrictSave, - orgId + orgId, + workspaceId }: IExecuteVectorStoreInsert) => { try { - const entity = await saveVectorStoreConfig(appDataSource, data, isStrictSave) + const entity = await saveVectorStoreConfig(appDataSource, data, isStrictSave, workspaceId) entity.status = DocumentStoreStatus.UPSERTING await appDataSource.getRepository(DocumentStore).save(entity) - const indexResult = await _insertIntoVectorStoreWorkerThread(appDataSource, componentNodes, telemetry, data, isStrictSave, orgId) + const indexResult = await _insertIntoVectorStoreWorkerThread( + appDataSource, + componentNodes, + telemetry, + data, + isStrictSave, + orgId, + workspaceId + ) return indexResult } catch (error) { throw new InternalFlowiseError( @@ -1282,10 +1314,11 @@ const _insertIntoVectorStoreWorkerThread = async ( telemetry: Telemetry, data: ICommonObject, isStrictSave = true, - orgId: string + orgId: string, + workspaceId: string ) => { try { - const entity = await saveVectorStoreConfig(appDataSource, data, isStrictSave) + const entity = await saveVectorStoreConfig(appDataSource, data, isStrictSave, workspaceId) let upsertHistory: Record = {} const chatflowid = data.storeId // fake chatflowid because this is not tied to any chatflow @@ -1852,7 +1885,7 @@ const upsertDocStore = async ( } try { - const newLoader = await saveProcessingLoader(appDataSource, processData) + const newLoader = await saveProcessingLoader(appDataSource, processData, workspaceId) const result = await processLoader({ appDataSource, componentNodes, diff --git a/packages/server/src/services/evaluations/index.ts b/packages/server/src/services/evaluations/index.ts index 9195ac26f7c..3d1b13d4c9e 100644 --- a/packages/server/src/services/evaluations/index.ts +++ b/packages/server/src/services/evaluations/index.ts @@ -21,11 +21,12 @@ import evaluatorsService from '../evaluator' import { LLMEvaluationRunner } from './LLMEvaluationRunner' import { Assistant } from '../../database/entities/Assistant' -const runAgain = async (id: string, baseURL: string, orgId: string) => { +const runAgain = async (id: string, baseURL: string, orgId: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const evaluation = await appServer.AppDataSource.getRepository(Evaluation).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!evaluation) throw new Error(`Evaluation ${id} not found`) const additionalConfig = evaluation.additionalConfig ? JSON.parse(evaluation.additionalConfig) : {} @@ -55,13 +56,13 @@ const runAgain = async (id: string, baseURL: string, orgId: string) => { } } data.version = true - return await createEvaluation(data, baseURL, orgId) + return await createEvaluation(data, baseURL, orgId, workspaceId) } catch (error) { throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: EvalsService.runAgain - ${getErrorMessage(error)}`) } } -const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: string) => { +const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const newEval = new Evaluation() @@ -97,7 +98,8 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str ) const dataset = await appServer.AppDataSource.getRepository(Dataset).findOneBy({ - id: body.datasetId + id: body.datasetId, + workspaceId: workspaceId }) if (!dataset) throw new Error(`Dataset ${body.datasetId} not found`) @@ -124,7 +126,8 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str for (let i = 0; i < chatflowIds.length; i++) { const chatflowId = chatflowIds[i] const cFlow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: chatflowId + id: chatflowId, + workspaceId: workspaceId }) if (cFlow && cFlow.apikeyid) { const apikeyObj = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({ @@ -338,7 +341,7 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str } } -const getAllEvaluations = async (workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllEvaluations = async (workspaceId: string, page: number = -1, limit: number = -1) => { try { const appServer = getRunningExpressApp() @@ -421,7 +424,7 @@ const getAllEvaluations = async (workspaceId?: string, page: number = -1, limit: } // Delete evaluation and all rows via id -const deleteEvaluation = async (id: string, activeWorkspaceId?: string) => { +const deleteEvaluation = async (id: string, activeWorkspaceId: string) => { try { const appServer = getRunningExpressApp() await appServer.AppDataSource.getRepository(Evaluation).delete({ id: id }) @@ -437,11 +440,12 @@ const deleteEvaluation = async (id: string, activeWorkspaceId?: string) => { } // check for outdated evaluations -const isOutdated = async (id: string) => { +const isOutdated = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const evaluation = await appServer.AppDataSource.getRepository(Evaluation).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!evaluation) throw new Error(`Evaluation ${id} not found`) const evaluationRunDate = evaluation.runDate.getTime() @@ -456,7 +460,8 @@ const isOutdated = async (id: string) => { // check if the evaluation is outdated by extracting the runTime and then check with the dataset last updated time as well // as the chatflows last updated time. If the evaluation is outdated, then return true else return false const dataset = await appServer.AppDataSource.getRepository(Dataset).findOneBy({ - id: evaluation.datasetId + id: evaluation.datasetId, + workspaceId: workspaceId }) if (dataset) { const datasetLastUpdated = dataset.updatedDate.getTime() @@ -483,7 +488,8 @@ const isOutdated = async (id: string) => { } } const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: chatflowIds[i] + id: chatflowIds[i], + workspaceId: workspaceId }) if (!chatflow) { returnObj.errors.push({ @@ -511,7 +517,8 @@ const isOutdated = async (id: string) => { continue } const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({ - id: chatflowIds[i] + id: chatflowIds[i], + workspaceId: workspaceId }) if (!assistant) { returnObj.errors.push({ @@ -540,11 +547,12 @@ const isOutdated = async (id: string) => { } } -const getEvaluation = async (id: string) => { +const getEvaluation = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const evaluation = await appServer.AppDataSource.getRepository(Evaluation).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!evaluation) throw new Error(`Evaluation ${id} not found`) const versionCount = await appServer.AppDataSource.getRepository(Evaluation).countBy({ @@ -553,7 +561,7 @@ const getEvaluation = async (id: string) => { const items = await appServer.AppDataSource.getRepository(EvaluationRun).find({ where: { evaluationId: id } }) - const versions = (await getVersions(id)).versions + const versions = (await getVersions(id, workspaceId)).versions const versionNo = versions.findIndex((version) => version.id === id) + 1 return { ...evaluation, @@ -566,11 +574,12 @@ const getEvaluation = async (id: string) => { } } -const getVersions = async (id: string) => { +const getVersions = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const evaluation = await appServer.AppDataSource.getRepository(Evaluation).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!evaluation) throw new Error(`Evaluation ${id} not found`) const versions = await appServer.AppDataSource.getRepository(Evaluation).find({ @@ -597,12 +606,13 @@ const getVersions = async (id: string) => { } } -const patchDeleteEvaluations = async (ids: string[] = [], isDeleteAllVersion?: boolean, activeWorkspaceId?: string) => { +const patchDeleteEvaluations = async (ids: string[] = [], activeWorkspaceId: string, isDeleteAllVersion?: boolean) => { try { const appServer = getRunningExpressApp() const evalsToBeDeleted = await appServer.AppDataSource.getRepository(Evaluation).find({ where: { - id: In(ids) + id: In(ids), + workspaceId: activeWorkspaceId } }) await appServer.AppDataSource.getRepository(Evaluation).delete(ids) diff --git a/packages/server/src/services/evaluator/index.ts b/packages/server/src/services/evaluator/index.ts index 3cfbcc6f399..bd3d7e23a30 100644 --- a/packages/server/src/services/evaluator/index.ts +++ b/packages/server/src/services/evaluator/index.ts @@ -5,11 +5,11 @@ import { getErrorMessage } from '../../errors/utils' import { Evaluator } from '../../database/entities/Evaluator' import { EvaluatorDTO } from '../../Interface.Evaluation' -const getAllEvaluators = async (workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllEvaluators = async (workspaceId: string, page: number = -1, limit: number = -1) => { try { const appServer = getRunningExpressApp() const queryBuilder = appServer.AppDataSource.getRepository(Evaluator).createQueryBuilder('ev').orderBy('ev.updatedDate', 'DESC') - if (workspaceId) queryBuilder.andWhere('ev.workspaceId = :workspaceId', { workspaceId }) + queryBuilder.andWhere('ev.workspaceId = :workspaceId', { workspaceId }) if (page > 0 && limit > 0) { queryBuilder.skip((page - 1) * limit) queryBuilder.take(limit) @@ -31,11 +31,12 @@ const getAllEvaluators = async (workspaceId?: string, page: number = -1, limit: } } -const getEvaluator = async (id: string) => { +const getEvaluator = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const evaluator = await appServer.AppDataSource.getRepository(Evaluator).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!evaluator) throw new Error(`Evaluator ${id} not found`) return EvaluatorDTO.fromEntity(evaluator) @@ -65,11 +66,12 @@ const createEvaluator = async (body: any) => { } // Update Evaluator -const updateEvaluator = async (id: string, body: any) => { +const updateEvaluator = async (id: string, body: any, workspaceId: string) => { try { const appServer = getRunningExpressApp() const evaluator = await appServer.AppDataSource.getRepository(Evaluator).findOneBy({ - id: id + id: id, + workspaceId: workspaceId }) if (!evaluator) throw new Error(`Evaluator ${id} not found`) @@ -88,10 +90,10 @@ const updateEvaluator = async (id: string, body: any) => { } // Delete Evaluator via id -const deleteEvaluator = async (id: string) => { +const deleteEvaluator = async (id: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() - return await appServer.AppDataSource.getRepository(Evaluator).delete({ id: id }) + return await appServer.AppDataSource.getRepository(Evaluator).delete({ id: id, workspaceId: workspaceId }) } catch (error) { throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, diff --git a/packages/server/src/services/variables/index.ts b/packages/server/src/services/variables/index.ts index fb1f10a48ae..5b427e95488 100644 --- a/packages/server/src/services/variables/index.ts +++ b/packages/server/src/services/variables/index.ts @@ -32,10 +32,10 @@ const createVariable = async (newVariable: Variable, orgId: string) => { } } -const deleteVariable = async (variableId: string): Promise => { +const deleteVariable = async (variableId: string, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() - const dbResponse = await appServer.AppDataSource.getRepository(Variable).delete({ id: variableId }) + const dbResponse = await appServer.AppDataSource.getRepository(Variable).delete({ id: variableId, workspaceId: workspaceId }) return dbResponse } catch (error) { throw new InternalFlowiseError( @@ -45,7 +45,7 @@ const deleteVariable = async (variableId: string): Promise => { } } -const getAllVariables = async (workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllVariables = async (workspaceId: string, page: number = -1, limit: number = -1) => { try { const appServer = getRunningExpressApp() const queryBuilder = appServer.AppDataSource.getRepository(Variable) @@ -77,11 +77,12 @@ const getAllVariables = async (workspaceId?: string, page: number = -1, limit: n } } -const getVariableById = async (variableId: string) => { +const getVariableById = async (variableId: string, workspaceId: string) => { try { const appServer = getRunningExpressApp() const dbResponse = await appServer.AppDataSource.getRepository(Variable).findOneBy({ - id: variableId + id: variableId, + workspaceId: workspaceId }) if (appServer.identityManager.getPlatformType() === Platform.CLOUD && dbResponse?.type === 'runtime') { From 22b36684e141a89d3cdf07ee63cc4fae9fde7142 Mon Sep 17 00:00:00 2001 From: Vinod Kiran Date: Wed, 30 Jul 2025 15:34:26 +0530 Subject: [PATCH 04/10] Update EvaluatorRunner and index to require workspaceId for evaluator retrieval - Modified the runAdditionalEvaluators function to accept workspaceId as a parameter. --- packages/server/src/services/evaluations/EvaluatorRunner.ts | 5 +++-- packages/server/src/services/evaluations/index.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/server/src/services/evaluations/EvaluatorRunner.ts b/packages/server/src/services/evaluations/EvaluatorRunner.ts index 4b2d7d81da1..3f2a42081d7 100644 --- a/packages/server/src/services/evaluations/EvaluatorRunner.ts +++ b/packages/server/src/services/evaluations/EvaluatorRunner.ts @@ -14,7 +14,8 @@ export const runAdditionalEvaluators = async ( metricsArray: ICommonObject[], actualOutputArray: string[], errorArray: string[], - selectedEvaluators: string[] + selectedEvaluators: string[], + workspaceId: string ) => { const evaluationResults: any[] = [] const evaluatorDict: any = {} @@ -27,7 +28,7 @@ export const runAdditionalEvaluators = async ( const evaluatorId = selectedEvaluators[i] let evaluator = evaluatorDict[evaluatorId] if (!evaluator) { - evaluator = await evaluatorsService.getEvaluator(evaluatorId) + evaluator = await evaluatorsService.getEvaluator(evaluatorId, workspaceId) evaluatorDict[evaluatorId] = evaluator } diff --git a/packages/server/src/services/evaluations/index.ts b/packages/server/src/services/evaluations/index.ts index 3d1b13d4c9e..fe0aae71b73 100644 --- a/packages/server/src/services/evaluations/index.ts +++ b/packages/server/src/services/evaluations/index.ts @@ -246,7 +246,8 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str metricsArray, actualOutputArray, errorArray, - body.selectedSimpleEvaluators.length > 0 ? JSON.parse(body.selectedSimpleEvaluators) : [] + body.selectedSimpleEvaluators.length > 0 ? JSON.parse(body.selectedSimpleEvaluators) : [], + workspaceId ) newRun.evaluators = JSON.stringify(results) @@ -260,7 +261,7 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str const llmEvaluatorMap: { evaluatorId: string; evaluator: any }[] = [] for (let i = 0; i < resultRow.LLMEvaluators.length; i++) { const evaluatorId = resultRow.LLMEvaluators[i] - const evaluator = await evaluatorsService.getEvaluator(evaluatorId) + const evaluator = await evaluatorsService.getEvaluator(evaluatorId, workspaceId) llmEvaluatorMap.push({ evaluatorId: evaluatorId, evaluator: evaluator From 4b3ab55c9c00ba322d11b0d32ac2dd9bfd7370d9 Mon Sep 17 00:00:00 2001 From: Vinod Kiran Date: Wed, 30 Jul 2025 15:42:27 +0530 Subject: [PATCH 05/10] lint fixes --- packages/server/src/controllers/apikey/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/controllers/apikey/index.ts b/packages/server/src/controllers/apikey/index.ts index a13f02f7f20..677d689311c 100644 --- a/packages/server/src/controllers/apikey/index.ts +++ b/packages/server/src/controllers/apikey/index.ts @@ -42,7 +42,7 @@ const updateApiKey = async (req: Request, res: Response, next: NextFunction) => } if (typeof req.body === 'undefined' || !req.body.keyName) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.updateApiKey - keyName not provided!`) - } + } if (!req.user?.activeWorkspaceId) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Workspace ID is required`) } From a8cc737d667035cedf7ca899a5ac3fd533f39a38 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Mon, 4 Aug 2025 22:12:47 +0530 Subject: [PATCH 06/10] Enhancement/Integrate workspaceId in chatflow and flow-config services - Updated chatflow and flow-config controllers to require workspaceId for fetching chatflows. - Modified service methods to accept workspaceId as a parameter, ensuring proper context for chatflow retrieval. --- .../src/controllers/chat-messages/index.ts | 2 +- .../server/src/controllers/chatflows/index.ts | 25 ++++++++++++------- .../src/controllers/flow-configs/index.ts | 9 ++++++- .../src/controllers/marketplaces/index.ts | 6 +++++ .../src/controllers/predictions/index.ts | 4 ++- .../server/src/services/chatflows/index.ts | 9 ++++--- .../server/src/services/flow-configs/index.ts | 4 +-- .../server/src/services/marketplaces/index.ts | 2 +- packages/server/src/utils/index.ts | 2 +- 9 files changed, 44 insertions(+), 19 deletions(-) diff --git a/packages/server/src/controllers/chat-messages/index.ts b/packages/server/src/controllers/chat-messages/index.ts index b000c9ea3fb..d1d7cccbab7 100644 --- a/packages/server/src/controllers/chat-messages/index.ts +++ b/packages/server/src/controllers/chat-messages/index.ts @@ -166,7 +166,7 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc ) } const chatflowid = req.params.id - const chatflow = await chatflowsService.getChatflowById(req.params.id) + const chatflow = await chatflowsService.getChatflowById(req.params.id, workspaceId) if (!chatflow) { return res.status(404).send(`Chatflow ${req.params.id} not found`) } diff --git a/packages/server/src/controllers/chatflows/index.ts b/packages/server/src/controllers/chatflows/index.ts index 000a2770a42..a7ee59703f3 100644 --- a/packages/server/src/controllers/chatflows/index.ts +++ b/packages/server/src/controllers/chatflows/index.ts @@ -107,7 +107,14 @@ const getChatflowById = async (req: Request, res: Response, next: NextFunction) if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.getChatflowById - id not provided!`) } - const apiResponse = await chatflowsService.getChatflowById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: chatflowsController.getChatflowById - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await chatflowsService.getChatflowById(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -162,7 +169,14 @@ const updateChatflow = async (req: Request, res: Response, next: NextFunction) = if (typeof req.params === 'undefined' || !req.params.id) { throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.updateChatflow - id not provided!`) } - const chatflow = await chatflowsService.getChatflowById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: chatflowsController.saveChatflow - workspace ${workspaceId} not found!` + ) + } + const chatflow = await chatflowsService.getChatflowById(req.params.id, workspaceId) if (!chatflow) { return res.status(404).send(`Chatflow ${req.params.id} not found`) } @@ -173,13 +187,6 @@ const updateChatflow = async (req: Request, res: Response, next: NextFunction) = `Error: chatflowsController.saveChatflow - organization ${orgId} not found!` ) } - const workspaceId = req.user?.activeWorkspaceId - if (!workspaceId) { - throw new InternalFlowiseError( - StatusCodes.NOT_FOUND, - `Error: chatflowsController.saveChatflow - workspace ${workspaceId} not found!` - ) - } const subscriptionId = req.user?.activeOrganizationSubscriptionId || '' const body = req.body const updateChatFlow = new ChatFlow() diff --git a/packages/server/src/controllers/flow-configs/index.ts b/packages/server/src/controllers/flow-configs/index.ts index c0926266085..ba5fadeee38 100644 --- a/packages/server/src/controllers/flow-configs/index.ts +++ b/packages/server/src/controllers/flow-configs/index.ts @@ -11,7 +11,14 @@ const getSingleFlowConfig = async (req: Request, res: Response, next: NextFuncti `Error: flowConfigsController.getSingleFlowConfig - id not provided!` ) } - const apiResponse = await flowConfigsService.getSingleFlowConfig(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: flowConfigsController.getSingleFlowConfig - workspace ${workspaceId} not found!` + ) + } + const apiResponse = await flowConfigsService.getSingleFlowConfig(req.params.id, workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/controllers/marketplaces/index.ts b/packages/server/src/controllers/marketplaces/index.ts index 5d19d40e6cd..1984f3ad938 100644 --- a/packages/server/src/controllers/marketplaces/index.ts +++ b/packages/server/src/controllers/marketplaces/index.ts @@ -47,6 +47,12 @@ const saveCustomTemplate = async (req: Request, res: Response, next: NextFunctio } const body = req.body body.workspaceId = req.user?.activeWorkspaceId + if (!body.workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: marketplacesController.saveCustomTemplate - workspace ${body.workspaceId} not found!` + ) + } const apiResponse = await marketplacesService.saveCustomTemplate(body) return res.json(apiResponse) } catch (error) { diff --git a/packages/server/src/controllers/predictions/index.ts b/packages/server/src/controllers/predictions/index.ts index b7f79b642c8..eef1186c382 100644 --- a/packages/server/src/controllers/predictions/index.ts +++ b/packages/server/src/controllers/predictions/index.ts @@ -25,7 +25,9 @@ const createPrediction = async (req: Request, res: Response, next: NextFunction) `Error: predictionsController.createPrediction - body not provided!` ) } - const chatflow = await chatflowsService.getChatflowById(req.params.id) + const workspaceId = req.user?.activeWorkspaceId + + const chatflow = await chatflowsService.getChatflowById(req.params.id, workspaceId) if (!chatflow) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${req.params.id} not found`) } diff --git a/packages/server/src/services/chatflows/index.ts b/packages/server/src/services/chatflows/index.ts index 9ce0a529b03..b3dfa7b28fe 100644 --- a/packages/server/src/services/chatflows/index.ts +++ b/packages/server/src/services/chatflows/index.ts @@ -229,11 +229,14 @@ const getChatflowByApiKey = async (apiKeyId: string, keyonly?: unknown): Promise } } -const getChatflowById = async (chatflowId: string): Promise => { +const getChatflowById = async (chatflowId: string, workspaceId?: string): Promise => { try { const appServer = getRunningExpressApp() - const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: chatflowId + const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOne({ + where: { + id: chatflowId, + ...(workspaceId ? { workspaceId } : {}) + } }) if (!dbResponse) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found in the database!`) diff --git a/packages/server/src/services/flow-configs/index.ts b/packages/server/src/services/flow-configs/index.ts index 8ce05499f2c..7755e86f39e 100644 --- a/packages/server/src/services/flow-configs/index.ts +++ b/packages/server/src/services/flow-configs/index.ts @@ -6,10 +6,10 @@ import chatflowsService from '../chatflows' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { getErrorMessage } from '../../errors/utils' -const getSingleFlowConfig = async (chatflowId: string): Promise => { +const getSingleFlowConfig = async (chatflowId: string, workspaceId: string): Promise => { try { const appServer = getRunningExpressApp() - const chatflow = await chatflowsService.getChatflowById(chatflowId) + const chatflow = await chatflowsService.getChatflowById(chatflowId, workspaceId) if (!chatflow) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found in the database!`) } diff --git a/packages/server/src/services/marketplaces/index.ts b/packages/server/src/services/marketplaces/index.ts index 6f7f9c6b654..c7a3b26a18c 100644 --- a/packages/server/src/services/marketplaces/index.ts +++ b/packages/server/src/services/marketplaces/index.ts @@ -211,7 +211,7 @@ const saveCustomTemplate = async (body: any): Promise => { Object.assign(customTemplate, body) if (body.chatflowId) { - const chatflow = await chatflowsService.getChatflowById(body.chatflowId) + const chatflow = await chatflowsService.getChatflowById(body.chatflowId, body.workspaceId) const flowData = JSON.parse(chatflow.flowData) const { framework, exportJson } = _generateExportFlowData(flowData) flowDataStr = JSON.stringify(exportJson) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 7d76471a3b0..8b834e7b62e 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -834,7 +834,7 @@ export const getGlobalVariable = async ( id: '', updatedDate: new Date(), createdDate: new Date(), - workspaceId: 'dummy' + workspaceId: '' }) } } From fe4e59ad8b8c1e43b64990fc5ef38f1f3acd78c1 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 18 Aug 2025 19:46:35 +0800 Subject: [PATCH 07/10] lint fix --- packages/server/src/controllers/predictions/index.ts | 2 +- packages/server/src/services/documentstore/index.ts | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/server/src/controllers/predictions/index.ts b/packages/server/src/controllers/predictions/index.ts index eef1186c382..d467f3157e4 100644 --- a/packages/server/src/controllers/predictions/index.ts +++ b/packages/server/src/controllers/predictions/index.ts @@ -26,7 +26,7 @@ const createPrediction = async (req: Request, res: Response, next: NextFunction) ) } const workspaceId = req.user?.activeWorkspaceId - + const chatflow = await chatflowsService.getChatflowById(req.params.id, workspaceId) if (!chatflow) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${req.params.id} not found`) diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts index 6c9171ffc4a..0ee1cad2015 100644 --- a/packages/server/src/services/documentstore/index.ts +++ b/packages/server/src/services/documentstore/index.ts @@ -311,13 +311,7 @@ const getDocumentStoreFileChunks = async ( } } -const deleteDocumentStore = async ( - storeId: string, - orgId: string, - workspaceId: string, - usageCacheManager: UsageCacheManager, - isDeleteBoth: boolean = false -) => { +const deleteDocumentStore = async (storeId: string, orgId: string, workspaceId: string, usageCacheManager: UsageCacheManager) => { try { const appServer = getRunningExpressApp() From b36e40190c2b39138c3b8425d459d574ac1381e7 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 18 Sep 2025 01:10:22 +0100 Subject: [PATCH 08/10] get rid of redundant isApiKeyValidated --- packages/server/src/enterprise/Interface.Enterprise.ts | 2 -- .../server/src/enterprise/controllers/workspace.controller.ts | 1 - packages/server/src/enterprise/middleware/passport/index.ts | 1 - packages/server/src/enterprise/rbac/PermissionCheck.ts | 4 ++-- packages/server/src/enterprise/sso/SSOBase.ts | 1 - packages/server/src/index.ts | 3 +-- packages/server/src/routes/chatflows/index.ts | 2 +- 7 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/server/src/enterprise/Interface.Enterprise.ts b/packages/server/src/enterprise/Interface.Enterprise.ts index d7ddfc393d6..5dd4384e043 100644 --- a/packages/server/src/enterprise/Interface.Enterprise.ts +++ b/packages/server/src/enterprise/Interface.Enterprise.ts @@ -17,7 +17,6 @@ export class IUser { role: string lastLogin: Date activeWorkspaceId: string - isApiKeyValidated?: boolean loginMode?: string activeOrganizationId?: string } @@ -73,7 +72,6 @@ export type LoggedInUser = { activeWorkspaceId: string activeWorkspace: string assignedWorkspaces: IAssignedWorkspace[] - isApiKeyValidated: boolean permissions?: string[] features?: Record ssoRefreshToken?: string diff --git a/packages/server/src/enterprise/controllers/workspace.controller.ts b/packages/server/src/enterprise/controllers/workspace.controller.ts index d20edcf9449..dc29f97a654 100644 --- a/packages/server/src/enterprise/controllers/workspace.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace.controller.ts @@ -129,7 +129,6 @@ export class WorkspaceController { activeWorkspaceId: workspace.id, activeWorkspace: workspace.name, assignedWorkspaces, - isApiKeyValidated: true, isSSO: req.user.ssoProvider ? true : false, permissions: [...JSON.parse(role.permissions)], features, diff --git a/packages/server/src/enterprise/middleware/passport/index.ts b/packages/server/src/enterprise/middleware/passport/index.ts index 5cbf0b16bc0..083c93e178c 100644 --- a/packages/server/src/enterprise/middleware/passport/index.ts +++ b/packages/server/src/enterprise/middleware/passport/index.ts @@ -165,7 +165,6 @@ export const initializeJwtCookieMiddleware = async (app: express.Application, id activeWorkspaceId: workspaceUser.workspaceId, activeWorkspace: workspaceUser.workspace.name, assignedWorkspaces, - isApiKeyValidated: true, permissions: [...JSON.parse(role.permissions)], features } diff --git a/packages/server/src/enterprise/rbac/PermissionCheck.ts b/packages/server/src/enterprise/rbac/PermissionCheck.ts index 583d4ad604c..d0856c4bc9e 100644 --- a/packages/server/src/enterprise/rbac/PermissionCheck.ts +++ b/packages/server/src/enterprise/rbac/PermissionCheck.ts @@ -7,7 +7,7 @@ export const checkPermission = (permission: string) => { const user = req.user // if the user is not logged in, return forbidden if (user) { - if (user.isApiKeyValidated || user.isOrganizationAdmin) { + if (user.isOrganizationAdmin) { return next() } const permissions = user.permissions @@ -26,7 +26,7 @@ export const checkAnyPermission = (permissionsString: string) => { const user = req.user // if the user is not logged in, return forbidden if (user) { - if (user.isApiKeyValidated || user.isOrganizationAdmin) { + if (user.isOrganizationAdmin) { return next() } const permissions = user.permissions diff --git a/packages/server/src/enterprise/sso/SSOBase.ts b/packages/server/src/enterprise/sso/SSOBase.ts index f990de5a85f..e216c6977ee 100644 --- a/packages/server/src/enterprise/sso/SSOBase.ts +++ b/packages/server/src/enterprise/sso/SSOBase.ts @@ -132,7 +132,6 @@ abstract class SSOBase { activeWorkspaceId: workspaceUser.workspaceId, activeWorkspace: workspaceUser.workspace.name, assignedWorkspaces, - isApiKeyValidated: true, ssoToken: accessToken as string, ssoRefreshToken: refreshToken, ssoProvider: ssoProviderName, diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 40666c5c5fa..88162738cdb 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -267,8 +267,7 @@ export class App { activeOrganizationProductId: productId, isOrganizationAdmin: true, activeWorkspaceId: apiKeyWorkSpaceId!, - activeWorkspace: workspace.name, - isApiKeyValidated: true + activeWorkspace: workspace.name } next() } diff --git a/packages/server/src/routes/chatflows/index.ts b/packages/server/src/routes/chatflows/index.ts index 774f0928dcb..a043d8dacfa 100644 --- a/packages/server/src/routes/chatflows/index.ts +++ b/packages/server/src/routes/chatflows/index.ts @@ -18,6 +18,6 @@ router.put(['/', '/:id'], checkAnyPermission('chatflows:create,chatflows:update' router.delete(['/', '/:id'], checkPermission('chatflows:delete'), chatflowsController.deleteChatflow) // CHECK FOR CHANGE -router.get('/has-changed/:id/:lastUpdatedDateTime', chatflowsController.checkIfChatflowHasChanged) +router.get('/has-changed/:id/:lastUpdatedDateTime', checkPermission('chatflows:update'), chatflowsController.checkIfChatflowHasChanged) export default router From 2ba94500c1d03248d56c627c364ef38d8bac45a7 Mon Sep 17 00:00:00 2001 From: Vasudevan Ramasamy Date: Mon, 29 Sep 2025 11:33:28 -0700 Subject: [PATCH 09/10] CORS POC --- data/database.sqlite | Bin 0 -> 385024 bytes opa/data/data.json | 139 ++++++++++++++++++ opa/policy.rego | 0 opa/policy/policy.rego | 29 ++++ packages/server/src/index.ts | 20 ++- .../src/services/domainValidation/index.ts | 102 +++++++++++++ packages/server/src/utils/XSS.ts | 51 ++++++- 7 files changed, 331 insertions(+), 10 deletions(-) create mode 100644 data/database.sqlite create mode 100644 opa/data/data.json create mode 100644 opa/policy.rego create mode 100644 opa/policy/policy.rego create mode 100644 packages/server/src/services/domainValidation/index.ts diff --git a/data/database.sqlite b/data/database.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..6a726cf955f28e4a6470af1049429286536eb1ec GIT binary patch literal 385024 zcmeFa33MDudM1V<34j-fK8Azo>FHs1b#nj=kz}5gnbn%@0m))Fh9tno(Z{q}P*!Fn zkj<*f>N-fGTeO6po&@r`=lU@jg9>&e7*}li|{!HpKrqF5PagFQv79)zkfP*w)3;&(|2FQ{_tU>~cv+0yJDQvc?I zo>-WeS(z`5`7M{+>b}wd%r@C)#mdv?7+oNc?e*(BYry_NZX|Ap1s;(AS3Up#-MCX~}`Wnr%DyJg{q-2rk(xVE}< zb#e6t;d1Q-VR>a;Sl+mDWkD!+f(A*LQUO$!*9dN=Wu$|HRu8gK7JB4XFDMMAVJ;4un{5R*XXoc3Vw#cloi@Dp?BeQ$rx#ZTUz=T8Uaze#FJ76A_?G~< zJU0Y2Ni*o|WP|~=3*`bCNWBBZ2C>~*)Ak!>VZHYJ`uy(8^OG|tPnJGoumYvQ^m8&> zfRr?1!QH8RAlEcSaq*{$u4Oj?3|elGP7IHv!U6i2eU9|1Md#t9n$SL z3hD!`L!h?yU`?UAx-Vs(U;5a@%uB{FT&YIHMq<+=#I4)T^-Z~ueEjuKoSvL9jM5t+ zc!f-b#A8_~3wiDiqEz${6^=~dkkAeol-5?E?mKC-1?vB%ReX2i@yVH`)1}=r%&sq8 ze14PYhNdfqswj$~c&@3Mp5{rO>Z)mw4n_DaEnloXFO>Iwu`H}C51Mr29JrW}8}@AYFJo}78)k;R^MWJ{C7I57%_Q7 zT}X@)H_uQ!H|$0~vPWrhkC}6b0g&jtg;Io9`)yhc_usJ0%MVS=)J_e9&xR(U+q0pa zm-*L;*FW;1$(d89N^eXu0C@oaGMYZRfD4)&o| ztaTh7_6{LB`+>b$jUMzJHf8$z=AX1D$8WXDgsoz#**^6EkFLeTPjXp`SLm4~!I`r>rZPC2Et?8Z)y z<{~0Epdwq)+vJ39o4}B3Jy?hSa@3c)tbC#oUYESm2mK?t$Ag)&$_36* zyR!gq&hM5>tYW=B&15$HE!u~HK~eJUKt@tI35bi`hXL6Z$qw`uH`Z5{mf_8-wPhIc zCBr^Ce1i6=2}8}b-Ba*;W%nffUf!jn_ba<}?Ec|4{1R`&ukmN#*F$qt@LQVvH)GTP z^YpJye|6fQzBK*liN8GYyC;6`#O{fe6OT^)uT#G~^`$9)syg-I<9~AeKRy04$6q~u z;rK_7{i|dD=-5vm>mR#z?9rqD&(VK!^rwzGN6n-69r@Qs{?U=I9cdhS^2h^+|KRX{ zeE9ngUpssXBETO+01-e05CKF05qL)tn3oQXjbASP#Lqt3|7TYN*Y|cVM3WD67F0HL zU8+Bpb?tJ7x}UANZh1fBJuM#ucD^RwD-0l#KdG#$hnnj!-KOQjzl`@pCF;*x)B zrA=CEY_fSV9&E4E;UI9TkRnO1$fh~Jz|#Mtzp(VzGL0inR}DtPy&c(5P05l>L#Zgb zdrJVUVys}dIk$?QZ z{C|f)`Zdz&2GF7883txeqFFF>O*%6lGXLDq|BJ5x^BQ?&Iam*<+%D;@1~-8j(yhp{ zRk14bb1dCI^1Yw^mFvJv!Cnt|J}Ijt%Zepgrt$bJur8Lq`bU5L-Op3GYbzImM!(tW z0+*!AmZlq~Db0V33H<6$yyUyIP*yrycFTX20Zq$Av1C=1Wx1j&A3F`KwbECA;ybpN z2g;A~NtR|pF@Egvknic={@SlY#A%5j&oC@gqX6jhkA+c>um9#h0lrH?0|pu!?Q5N& z+3rCql?wbQmZr$6UItdD^wmTE(YO8-SUHeOuyk;t57WTSb<%7%ppVX8({)3aR8yUQ zG>qZU-}{7;er*k=hX~Ca=$mFKiUh?W0P72-AO6Gt{X70WU|sC=d|K39kPnJ|HC$q$ zZ^%06gsD~Zxl{1QQt5~P>6d=K0dEW>1Nbacg%}j^qrkaV`pQ2zKX#DHc4!UA3zj9r z1ZC3|^|42Q{mIf-KKsk#-;qu=#4agNzPhZL+T$MqrbWh7r84`yZKmZEH_oL94+EFQ zxJ=5`_Ae9A?u0{{`!H}Um44`_yFWJu9HBzdEMJ1ckPW$_O3{!05O6xBAKG2|@+sha zme7i@MpvuCu@mTQ;ILi;mlM$@?kvr?zyMWCmNetc zgTS~_`ttYw?GG;k;}uw~$fr-%RnYN@EUO=V0GRdCmp8t+MOB0{L#&diLTxrGvLfCO z;cKNYf9MZ>;};-&gRN^k9WHCcmI$h{tU;-(x>>#t*gsMF((T{>J}T}~G?g!d;ek3* zku6m+KQ;|KwbGX^U;6i5;8{zJQh~3cRx}F?RYg)x0N+ccAFS@)|7|J@*4(1CwOn?< z!kQ)&rv?Qz1@Dm34}9zR@Bec2PHLSl|DL2kRux%MjfLaz-ZP~i_-B9l@_!BQU5JL# z;R{U9DwbtQk_G(mIR@M>lz!ls{`xzA9=JE+^}(!sctbPgiUfv4o;wO}ER}xX@BQ+Z zUxYWFji#t01c6gkbOTJSF3%qU&dZ#W8_ojO(}#ieLg|Y?yz|3PRJ$s!5>&iIHNLwm?cOXDD1-}U^`d(;&Ie}89@XP0Ym^1Km-s0 zL;w*$1P}p401-e0?sWuk{r_Iq0L(ukfCwN0hyWsh2p|H803v`0AOeWMy?{V!{eKej zJ8>`M8S{t;AOeU0B7g`W0*C-2fCwN0hyWsh2;6H3;QIf)R^pgnL;w*$1P}p401-e0 z5CKF05kLeGfqM~w)cXICdoll*XG8!IKm-s0L;w*$1P}p401-e05CKHsUO)iX|L=uT z#ylbdhyWsh2p|H803v`0AOeU0B7g|o>jPv-R|w2MB7g`W0*C-2fCwN0 zhyWsh2p|H8!21#bT>pPx7As~75kLeG0Ym^1Km-s0L;w*$1P}p401?O_kXrv|QvsPk zd_V*c0Ym^1Km-s0L;w*$1P}p401-e05P|mv0=WMFz9>e_1R{V4AOeU0B7g`W0*C-2 zfCwN0hyWt6CjzPU|LHv$F%S_z1P}p401-e05CKF05kLeG0Ym^1Km^{i2;lnvd$#B> z;fMetfCwN0hyWsh2p|H803v`0AOeWM$Oxp?|L=#F(EtC)$RQ&lfCwN0hyWsh2p|H8 z03v`0AOeU0BJkcp0N4NDJ7t9_MFbE5L;w*$1P}p401-e05CKF05kLgqECQ+Z|2HcF z-$Mit0Ym^1Km-s0L;w*$1P}p401-e05P^3&0=WMFE-xTV9wLAUAOeU0B7g`W0*C-2 zfCwN0hyWsR7ZAYp|GNMpia-Pq0Ym^1Km-s0L;w*$1P}p401-e0-sK43`v1GUfG~N8 z03v`0AOeU0B7g`W0*C-2fCwN0h`?PyAhrHKa~H&+2t)u8Km-s0L;w*$1P}p401-e0 z5CKF05qK9OFg5Y}V@Jk+er)>1=_8YekF6j5FOK}ckx!2Q(fH37{X6t)V}JW^FcY&M zpO|^#WNFNAx#U*&m4@FVn|8k!u;I=f<+6%(+%DS+; zaplT_Q0@c`k}#zLs4TA$+)T?z2M4VlY4yrNkKF2|1$Kje#~~L3r{9F|ZpZ);^}EDf z4}wNQ@%#o^Y_)=(-SdN1mcG$$1h$*doDQ*j#Jvc=%7P2Odw!GfXoZWlON$#<)`ht^ zXl}L@+?<`ChlpuL)_2*XpejyBoCp)>cAxXzr88PS5vjhXNP9=LHQY zhK=^MPS9+}^!bV3Kk%Q;oH|wd+-r$~(Bfh$K)*~Cs0O8vs244}S*#aS1{8xmSn@7` zLfat#T^-7q*e%Gk-@VqaH+&~ev)lf4vXjy);G@Z|2fYhH%ku#ns#KKv=1aZbrmFC! z6b8~4rz0-9mfhIt(Og6X2V`%7DsV!#O`u|~2kVeYybb=$eVDHoEDzy68SlQV`*D_&qWopjgeEwp0uyAzL3&MciS z?Ve%%!=;PQZxY?mbj45=MKKi5HC5BoJjqjCH4WsU2*0J}i?!#4^4>3&g_Y$j#uQpA zj+!ptEzb+aV-qu%PmiP%8C7^-Z0z9oTzF*Ii?Q9>l;dyr!3&pbr-r>?Lr1IIvs=AQ znSY%)@w*3piS~rvIKvDb58z+Mv)aVzBWjRKdeDuFx}s?Xf!>J=T{_=}K3;(u_@D=C z9q2PeVQ)bb;MF(ZpUL+;=1=@?>3fF{LVD15+mz|=qrmtq?TUQ%2t$_A=;uflPf8jQ z%AuV~mvo(u-wu1iv{RA6IcNkO4r4l`0OGE*4Lud!Gx7Q@D1o=wO}V@9s}D@fJoV@> z6;G53tSVQq!_X^Kht~^M_i1g7EHV9h;@W|UnKNffFR-$U#+qI7N}seGl6yRuDbZZu z94)m4cyoTYT$-GD^wHAm(@bX5-=bC!hHlBX0~tx>Bp}uhABK%vBs&~k+*n^(T81~T z)|O#lnhZ|qAP?$$vkk)se+yns2No;S{M3(Glw z{{G;p@&LuTn;S<8<&Kmllg2uLSmeUxUy%5)UQ8cO)@(a))wfM zHTpJmT=|#TtV~WH5c^7da^@3{ly*-tv+ujNHe>s^nX1e&?Q$eGGWNsRLo*-0RGurR zM3v{q#>SuAkD-1XUN}L!qMr|kDY4x6?_=4##$?FqQ6cv;!Ysw$JIT-_r4oZ|v<#wQ z%AS1TNDWd6HL0^83<`SlBhf)wZ{Cy-%1RevIIbMvxwNucTY74lP5k6aU0AJMs;$)jrIb} zSM}}2$m~(FgASkH8%#r^(X7Q5G@1+JvwUpoa`2TV>2-XEchuunT4Wk|zMQ}XTN^jTistgxp+~UK3E&W< zCL}KIiO=ii#LUuTcQPy;+MkLH#U?y_?DZ?gW?J(gzW-)ID={Vun09)bb6>BG|!UK_7YjhB!I%>+5CKF05kLeG z0Ym^1Km-s0L;w*$1m1ZBaQy$ymjR{#5kLeG0Ym^1Km-s0L;w*$1P}p401gjOe7+J2p|H803v`0AOeU0 zB7g`W0*C-2fC%g#0bKvzKW6v_B7g`W0*C-2fCwN0hyWsh2p|H803z^SLm;*OKl@%w zJEj*AKm-s0L;w*$1P}p401-e05CKF05kLe+Lx8UTADH^Su@k=rzwieUKm-s0L;w*$ z1P}p401-e05CKHsZA0MoPaHV1;k#IO-sNk}61MRkNxJ$1hDX9LY5+x>(m`L)1XKM2moW z>1N$=wTk1~l1bP94^02!*z_+>fAeiqaukdRAOeU0B7g`W0*C-2fCwN0hyWsh2p|Ic zL16CSMro93q64!BH;#-lA41pvONXQ}_-{>rdp}7*UPJ&9c>f~sgNG)^uTFmLiJjKc z%K6I5^II=J{k3Zczjk#TsK0Ra*(=Y~pMK`%R)1~n!t%3Mw=X)XcK!U*FTY%G)UIrM z=g;r_*iWwhH~;R~Yh@=0uKT21eXSgH{4Kv_H?G>Z7Po-X>Ngq-Wpb=LKn zfk4l0bjd<_yVq;4v>H3*YOm9W=eFJ3E?3Lv$_wSL-6U&%4|vNB`&Rij4eWN?L8nI} z>vu_q{`Fl5mn)uSd1_tM4aF2?w=RjM?ZWiB2oO3!gDg?mh9wczsav9Ix-e^BH$BlLb@(Nl70s4O1*Y(U-*$Tb z4YJbNvRnSEcFzx5>}{Anhv|LG71cVVOw&{a=KO6N=4f4*-j*!KHtL0Mul4I)r{lL{ zNgj}Vv^;++!{r80`ysWUja#IZO>K(8lbfRP^rmR(1gFVyic>V6nTEj=4%<$2TlM+5AseoeZ_Uu-VOQY2@qHvKZ8kd`*apCZS3vl{i z&Ux9VNL*f;j|)r_3ng~9ZD=eO$~WweZ`T`y$B__t3<-(LPe@$4-S)4O9WEz+NB=9111E=2v4Ay!9G;}cvIVz7JN9Xb6=sda{-F98E5rA=|mM#|-zB4qq-66Dg zitSF|kS?tWx%c~RXjFS5*F&hKc9$f2!k?4O#2iUBVhYayxdlx=ZN@k>DN4e1!*29# zKrrD-C_Fxr(0Gg~T3%Wv((qMA%FAQKyg)`Ql-TLF0K6V)wi{rvy49xLxlVfU;80yn z=niX}3rHdtn=sQ_%Ij!CrdG9p9Euj>`f)REuw(5@2t2O|iRUySQMF=Ss)cew?6(?z z>-r#_r#@ljNlZw*eo(m-B-$_n7_@~-J$Ac664nT|c=^M3zqLraReo2JJ$$1<2RL;w*$ z1P}p401-e05CKF05kLeG0Yu=vh`{3qua>S`uRhypZeL&RzG6K0LcjA2xwU9rf2ty` zsyDZ8U3}#PWxCTc-T2gzUmQDBvc|@P@k`^6l&s9ZmrLy5*dNe;2Rmc8$36xBhWyCS zOw80ymB##*OKx>vX~4?drrqxa?0J(;SZ&gYk4>3>{kXp}Ib%Ik+MQxNzI$skoJ`&1 zvmr}vNcmeAR%?svHDPJ_V(obW7WEkG8?RlPoH=!>^!>FSo&4p3`Iq_Vwe`jGS84*A zd1Hwv3v)+>f*-nCD8QsJtWI=7KHe{&)c^rXHK6k z{m?jzJH4iq{{Boh1?ixoM6d+(`@So<;b9qJj?V9O=DM)l*Xzt}lUs8gy9KlU^+r&i zlPmM{3xd2LnDfH-e2*|I&eA`7@mdRlR=_(ekMgD(Mph`myrTeFT@dz=P@FXbvZu_? z&kOC257VeS0!+>?#AU!sFkJnMOKwh78)a)xAhpw1l4Dgq2Xc+2)CcHSi9s7;GjXd!#p9vK-`8eeyCb7a8PzX*;?>ESlP61Gd5KvK zN~0eoYb7WNt;3y-nf&Ih!ybAa?gHVpSkd))ZY_j<%YOy-p=9)gnjxyY(K;v8XL4(( zaiML~dT^bz?!ZP_YIJK1=yA#l&@D5Ak%RH*^}D?3np4vDuiv^sI=<%9qVA0jabl)i#v^>~}%5kJ$E(c=3d^Ea|8=se|_f6nbP;~Fmsp-&OMr0Bbf^wYA;9Y z&JF2G6m*5O$CatZuwY84A>xn-7i*UmH?FJ;b8*nzY%91q3tiGd3l%l(VSgv) zA4L@Ja3>(8Mh9hKz4rY2V0@QWR%=U7EwjFRPGyDF+NIiRZTUiNEgBJUd7$02i;+n^ zsQ7{ZaptO6+SPfL<1cKo^>sI0ZSrAP2P<8kCH;C>>+&3f)$YKj*2H1eF8}tXoTuCe znL08filxs#$tqqxJpVkMt$O+3yH@wOV6ZK5CE!X@q{1QoOVFr}`UVV3o9k)C-&6kG zRNK2(o9?1^F!{>l!HFN6m>vJs@n;VG%R{doI)3nH4oU}pN9ZP#C4p&lZHOYlfse!oOTP-M6-p{nXN%6RS?c&&kbCvM}hC^oIG z*S)%Bh!x3(YZF|}hS-QFdKI`0gD9S+)ZP8ZrsiTRN3qFuV%96R0$#~1N7Njn0-nPh zNtEi6_0Xw7uzY0qyg7%cu-oh zD#7(1IuY$U+!>;{aOnkT34z--_8(im+&&t`=9!-2$%Y|XrtFHE;#zPyiROuxYDt=6 z6Wek2TW(TS&gftCVsaTSi_bGA*v(u>O5SfLa%W#MbmIW zML=Cdt5Vk?D%%GAAeLcQ3Ze@7J)t-*qpIgX^dE|{Wm|Q6b4(p-nL;bJ4eBUqbz)R;;mDF=Iz^RikMfeWs+^DQgOMW2 zwp7&4Ky>wd6Na%s_JT0${WWIM0wL(%ZB} zIUQwAvrJd6gN;&bsC8Oh1^1USL}|FL>KU$Q=$c%hCPOnvqtmKNUQbTMB}Y8Pl}H7e zY*0<`Ukawb;y|^NWw8Bl37Kl`GjozQ0Hr?_8Lo<@$r3R%ua{I@ls2p$~3U_ls{Q`{_HKJM~T)QQSP}Lm8h8#Qf(OMZE zm271d#6K2ARhMi_f{WC^9D~<1&9tD%EZqZ>Y1wYQ0@s8&?*4L>Z#$buBkhpj9<;ix zf{n6e+AjeAt7aV{wLH@>6w5TE%Dz;ef(TVRulh$~)dyd!h5|ir1Fn#RHcf`;bk&81 zO()P@C3V{y*@{pc&8h+wEoVji!x4_ABGrwGu8T6Dm>^sdXh+B=pic_7%x@J>*HU9ab?F&A&%4<=f)S!LlKS;kymlS@WXY0&=av_ zFpH)oi8{11wyDT&MJj64hFdGhnNrnqeXPd8SRdi$LU6DQt(pnuU$Z0!T5QN;#e&=R z>Vz1&TPZHK!ERQVIT>2)ypkN?nKLDi&=%X10Y}63z*;Gqs5mCvk|)+j}pgQ1cl(_wBMM!b+YQx@yg{8uyydU((pXr8{m%t@7WcNK=`*5h>jf9}Xn zjvf2XW4~}>>*zl?{@SslM}KJQ)YNYt`|OFYO#Rf{=Wo_nh$~elh%43vq zlogROWgKNO<59{u$~uG)Mt$ccgj1Apl-d7}vNVn`asLQq9A(b^BTV85)8!9S#!)7_ zKTH`%nW=t=F^(|L{2|IX$}I5=WgKOK_k)yil)2fHlyQ`4)elg{QD#IRWN92>GV=k- zILbWa{Va_m%pl&!7)O}=n`UVoVcPBlWgKP3ZHh9EGGBI_GLACab&N8OGFNq!GHN5t zG996eqf8JTri`OZ?@Thr5hi9P7~=?YD&v%Klo^pjlu;XDF5_S*@y^o_?Eb$|CJ?4h z{GVe7PCPkw;@`qA{6Pc|0Ym^1Km-s0L;w*$1P}p401-e0iV@hA4oq!4Ebfj^O+BQl zQ&Xc{w0tGJLpi_W;Bm!%T+$!!+*~p3E#0^-z5KL)^;P53X9HDTxwzg@mTuOcF`s+p z)}_le+BiA8 zJ3gjD!Z$X|x&!wXdzMHlb(rteU`GaQ{4ihxn?VTd%(g00{aM)iM;F}rz0hwZ$=Q_A zm3g%&7c3y=mDv_X)q-6!qoj8f9=iU2VEPxwrhjqzo5e+pR73y~Km-s0L;w*$1P}p4 z01-e05CKF05%?QMfUf_KGRMN!|3?}Bk5B(OjQ{_PW}`O!?dd;*e}DWpoF|O#Zx(^C zA1ytw@$ffFhYyd9sVa^8)W(>lXpUt&;P1eLgQ(!*K!W)%I0|dP0U@j6IIdNf7PsI^ zIzfJ|+zQ}6n{xHV*XVUA)iT^Z)#}CxCO?cga$#-S3sFy$;+DR`%%wVDwsu4hv(~j#SHVxnwWoT`0p1 zbZ%}4%E%zEwYN!=bnM1e(hRd3hX4|AuVBJ?jl$LKaWWw8 zfm4iYc5fTF&TYVbbKP@vYSV=S&aJa>#gN^#n>*(mDCl!uRH9iQsyp9kih2yXRbpl`(bCabVA}ateQ+eVLZao~pr+Tx>bp zL5J!bz|SbxY)ECOgt5{vkOYQuR&zLd5Lcv`L7VC#jXeHF)j5`Q0g6aqZvjK3gXv-` zP$Z^2a1|ldOqUbrAcIZ#3H4zi7^%+%I~%tHdG<1&I2hARhC0T(-D&*eBu5xs9Lma z8@5BXgN6%N8dl5K8U*T;aLI2pgcb0runskjVgZcZZK!ia^(Dat)ON4T@MJ)?qS$#h zJiE~y%tyMeWpa}Z*gH=bK;7x(vw{tof@}5~ZdU*+28GlM1ki|{-|CaQmcdlYM$RTH zU{*1x!bkedVWlNsU>)1U@x2|U)-?arKGIhrW#$#h?sPy+MlHUOJ6+Xi>`9^+(p+|; zj`6qjn`?0)^G1RddRkeR`^`G&9nW(sd``2R4+7B1R>D({2x+DjZY`p?;`C%|U8HvD zH?Ob;jVB;9Fg$}?PqVdzy+qMw=(C!du^!Lb9iLu!EG*OJkqc*OyOV7GF6GX~An1d&8PKE1sasZyUwoY1(e~2Wh?^JFLiA! zL%U*}#NoDwH{3$ha`Pf&_Lv9r&?rT*YUYcCHK>BuA>D0Oq68YD9Vi0g^m$7}U0t-H z0Kyh&8`@>sWr&+U8#-N4$EB!8mu@X`sS{I)8ro)1V+e_aK1nvGF)gcMt9`JUpqu~( zSXH-At2L_$Nsgk#A=;TZu_W3l4od>g z%(Dk-IG{_+I^g-d>|Ik>PSVL3sy$HUKx3mmG_?ESL(&Z#GV~BbzOY}zI{#5uKJMAA z?K!X*S`tuZUD6;)oKyrycQu5f6=s zz*V17kM@R7Zngs$@r0Ze-C$!QQPVRJm&4ZC%jr#k0`VjmYKM237rK8iI8#_4t(ioiI|0e7K_#8aq4SjFt#@8qN%~7XBic6Tn*j< zWSKb%Fu-lRr7Pf^R)Nz68aT)V&ysMkK&fjIk!)9X6`c;s=fS0mYw3-cY?KYf99Zc;5QDem;SJHt8^3u-Nm2A9jgO${n-R zd)e7m(C>b7t7-d5XCmiy}SF5P0Pi0 ziX(aF%X{}9TfW>r8pSrs4Z!=3P0s7&BXPNnaEh7Qn6PHA^&JQN(D16wu18VK0rh|R!w}Vxd3Ao2E0GJLqP~-g<1(sx z4n+Ts3vys%9>8_eGi0M!3DZIJ&{QP4A_$pes_9a#f%?9c4}% z;R^8L*orepqtmKNUQbTMB{#xF;>FPo%bcVQKIMU&hHNsu>c^v4@EeRa@&1z#)jeu(P_CpR^wo- zkB%yl3Y>zkn8X$}OLD}zYFlE(GCj9Wh@rcc;^>CqC_{^#SCRufbEf1GwIVv63`hA5 z+Y@a?(L}{D;X*#yvPeZLj%{e>BMPH91yzmNB{rm|3AD5Anh6&{r_|4 z)p-ZQ;M}_@#LoUB*T>WecTC#b{W1FgFSrnd`!EOh(sy(;j{g4>SI*HT50Mte{@PR5 zY@q*ta9o|eTOjMcA5<^7V*vgC(~bdmj2`O~`u~^E|3BPagZ}@~|Nnd1|G%M%b)xAa z_!G1h)%Hx&wQ&Fcy)o#=Tp|L903v`0AOeU0B7g`W0*C-2fC${%2%!J}d%OHG_lN)@ zfCwN0hyWsh2p|H803v`0AOiOW0(AU;sMH;s`p+lY2mcOy!JoGqfj7<_KDM#(=-6jB zRCQxRGG$HCw7RG|I$R{_dZt*n;n0WUxo|Mrb@jU8nzo`D70VUXx?+l&rYdmo#D*&+ z;U-Djku1kH>TCTr=`6ZUzx86-*|vLLBe>bE-tfsy*wK~{oDQ*jBtz=A-8{+dw;R4Q zKqU>5dzaj52c2FmXz2u}$#IHP zG@j%Xji)(9diKgw^qAhuv?3lJ` z8S07|oS5v#!@g%K(+oY*Q0$&v8Yo4(FeAL#~*LK8nU%CCxhN zguhW|xDvW=lg_}t(jaK?Dj7a-{SF_wYKMtDBx^k~;j7P1vI* z!_htuoI~u#_%SmmpdHK_R<1}XTb5{3Vu1H+2j8VS4Gmt0xKN{gJE?GK60aj^BCjNA zB1bMw>~7l~+J@e+JHB0S5MB)v0*@gfarp^}OSjwpb+W_d#E)D`OyDwN!hi%`gl^#U zn}ESuFX$vG%u#s+IXaIgN9WPy=r&w}-3Y+RL?;~A=FiKJ3j=#bD5Z9_3c*E9%m zEyt=z^-*}(`2WPS)c@bdPy9N3!5>5b5kLeG0Ym^1Km-s0L;w*$1P}p4U}OaRxyXeh zaa0(Ky5PRfC9ra7N)^$zTp5-x>vh@F3{zFKyz9Zx^I+Zya_F`l+~wsQrEbkmZp?;W zU}d=?Y1H|jOlLeiNwmxgxCS%~aF?gso^3OCd7p&2u^x2~5AhWE<&)!xw49Ga?_&@V zb%E?zbs1zB`PV0Y z{{)%*;)(B=`tQbXPW|1fa|gdG{5NvJ4`JLrxiL9&SuO2ae#<4deD~HSpOM>S6LHD& zl1rx39yI&tNI<`+UnBP#nl&t%e5DT<&|||dE?5Jh2qyZ`N=FwWX()S;}+eBCOUf)mCfE7iw#HWS|Hk`||@igt8p_(V5F4x32;l zT+)NvPr9R(SfsTDHM^ixd|0Vl>o}absN~)|S^)NErwyw5_N&B;FHFvyJX!kwhr&h# z7T)OM+f-H&DCw^0!0492C0(cEx9NK6C>j$qf(|c#Ub%}DVH9QQI@_cf*_I+1UcUv} zMlDZK=zB-i22A%|G&rBT_$)IxKlohO62T6^O}dCncLuUAT2{|E{T)y?nywLB6PL6b z!43hFU8f7xcB@DO{O+}W9riQs$rNs=fUZT{QWi;~sMLerg>XOPdrsvbk8#qOIV&*U zu-x0&=~1kPanNm{TR~@MI7hclh_k&OtV4|)&XqE&1?d=UjtiK)paH6~(Z1FRn(ZF6 zWpBHY?tj=-zWOd z=TYP4C7mEnJU^H!9+rL~Od1c$r%!%vFVy&Jn==2Z!}#^dXU+`E5?9J-%qr&y3%6G% zXBI&{W;y6|{IqG)?IPs4csEQm!lgl<9L#C)8>t~JWq_;>2rbXc&+L_9HU?$V!$BFCH)P5J$%p_VfCwN0hyWsh2p|H803v`0AOeWM`wsy+4R9Vl-vOUb!RIo3o`BCq z_&f=pPr_#jJ{RCa`RM0T^jw3#Pp3YVZvj4M;G@B389o+#O!(jrB7g`W0*C-2fCwN0 zhyWsh2p|H803v`0yaNc(^Z%tIKRpKj@dpt=1P}p401-e05CKHsokidW>(k>`Cx7DR zr=MMOZh7)^()m6(`&BJpJpb|wR%7eZ73G%mihb++&X0*d`**%I_FCBqg6lphS6?d! z9Ut73*^R6AtwnHeR&DhgjfFC}L-WBGVEotx4|w3mu|5pAG_c!cetsb`wjmY%!shvEc@(EpAQ299_zhsc zVR!pyTS343$t_Bu=W-#G9Zac~pM}84H5;7Ds+Q{&%g`#OA?luNi?Uj`!CfeUlecxr zb9KwsJWB)qaA)jFXUlH+uiEs?3VVALcV~rful4Kv^b3^~f;kw=1?E_fotGTS16$SI zzSCm~g2?=CI+@IEX$=41{@!X?mX(U(Se|IO3LHH)Rb8}Y)e#M1TSnb8!5g&Bv%vQo z19Izd=2?(w5IBn6Y7v(Qf;PMu6`gKqI;qG+G`%`VS2YdX6{~Pq+a)geAS1eESs)@R zoQuS>`;DH!B|#87{#h*tH(ShSU+|`i;Pyh4o)L2!rH5zn?k0j*mSQ<@Ze8*!9-P9q z>tfxhYoLPQwOh3+a^02^P-r5QyAFh6IFf5tbU0@&8&EJr7cHWz;QzbsxLU<=ZOP20 zLA~o~FyR!xW_mLCDVJnX1Lt#tm&s?-AkeyacM9VrT2mI={$;ZBtljYG*-)s5k(nZ$ zrVm;TPBlT5!SW^59^$_lZ@Mpo3=fW;g2e;A*Vy+e2x6{8p@vXlz)xAKk?+n>WKjtC z1I@kS`QSb>M`KEDjQfBdGEskRshFAlV$@vk#~B~%Lu*rZ&GW>&URHp*Z!B_h!&9ALDy%; zN1^WBDpzGmS|~%EfWM%lv>wt**|6(`_6pXhNO&nS?b#TBegogKfCbfEJVGHcAwj5r-Xb zt(G4%YBj?&fOFe-$x^G`@5M&Rrw@S9Yh{*p7Pd~$5m(t=3?VOAo1oKN_j}+op;`tp z^zmXQkr9!VD=bn-}(p2tf<014Jti#e}RxVqp8IwAw&)LvbSI(Qq@ z6rH0zF&8Xf>ukqv+R)?MIp;t@pYs|w&rt;ZE;$Fje6IsS-E;5;l)*Ww37bvm(SR)i zM#cmd2yTFA0gT;qF2u^1y5~5s=O7uKUika$%d}kfR1JpYV$0cPHupt@2xtRAVGf1UH$vgv@@)Xd4SQrc9rJ0= znJEmb4fOR*Nn~jf8JR^&VH0C4K$PDF$I;PHU?An8%5rFEI$~0UZ@1ThszuAT0q&o- zg9h~sUM*jPBf4E8T=E+YVWr;_)}h8xEP%1Q4Q8UKz9hJS+U}JZo(#xV6g$rb8{!P) zBVE@rxyc6Xou><+?$qatU_+)npETUA09FhNsTT;K5k0@fdKPylgQ=8_oK051tYT1w zkMx42JyT6`gQx~kFGlSDD3x$Hz8<8SFV z*WyCvjRY(7bRjSI=?y1|_*VFwW;qY%Q{mWn!V{e%hcRwaU01lZh~kRVldW};+NIyT z!WuLVdRYH?2DzSQYYBUaqRr4}H8qo*XQOKegk|cbg$rkCyOV7GF6GX~AQZw!qL=T87)ekm`c>pHiH^NNF=*EifL)oAjPz-hOPF&W`c4;bYNB8 zKCRZQCL}qEI%{!rm=szJepr}Ewg!tIhLTCy!`&N+j3Kr1QV?Wx76zisu16W7*Fwr z!vS4l)&bAwW$&88a*|HQQ0;*#2O1mop`qOmAChihG(*G$rn@yEU)ZnVbU-}rcma%i z3@nD01W4$@wNMV5c8K~C<(s}sCrD*Ug#iHZx3;18U#TSB*~NM{X!L1Ed|<>wBO)$z zq+t{VU+MiP@nkBDjVijq#zvy7NoVzi@~Z(%idL1|x3l*E=x`4}=Bg&8%1Db()t1*U zU0Hc8Q4n5_v^gZw)4Pnm-_H~itdTvnFu!>4c(ZL+7(AP1s85KfzC7A>Vw;Q z?zo$ZL516T;@#cv!EHUcp-*m`5R8R7_7)t)+=@=%(9K#*E7*Q`zD0?hn#k>gx2$MP zC7bCnRbabUNpCxF>G1*xh_^N|SwiY8S~dpg5%E(K3g7+3UV@+z&rDq0fnJsGz)t5K z*hm-zu$S9zxkA6^H~d!#YxO`7+!z*r408E(T7&2?rR~V3TA<9Orm;tp;0*T^DkYj;=XJp8P(u@~jOL8M zH@l3s+3pa!;}pWOJ+$x@HtPx8{BS@6(1MNQev1yJ+1aMdKIjWj$KWhek90sT{7#~z z$Hz9|Y+WiO8{u;u@F~Dr5NIrfYdg@YwcyAM$OD{gp$lzGJ-9_&XhLrIouEZ+awhJj zCcq~=6&5KiNv`QW4cUweI}FLj9lZomQBF9r1E(Ea0phpAJ8awRPC0h2Dn7Xbe67$% zfW4q6yP_5aOlDz|9v!k`uML*4m@8OrD+UG_Lp27ndnYq7aO{B!OJHJYTZ^_fM?ta3 zI~dD2Rn&^6ZDqQ)u98>!;Bi8Dy5F>0`2%kQaYx);#?4MtBpFP$sb?#|NR^c(_0Bbw zx9-AR+yj8|4ebKZV=!qu%{gPfsHZSc9yy(ZTi}G9px+6PnX=Pi^uA=f4*S*x*Y`Zq z0TmP6sQ)2!f!873vqBsXY{jSPfv!||Qm5Yr6A8Q0S%q=o@TNn~LRtsg0WN^0G|5gJ zRZJeS@T2^u%laZaJ^-hOI5?SD*c%~Fc#SN_il4WLYAfx24RFVY{WHf2d6YNlF-v+l z;d*1X9-qVRMlh5{Er5VT~0VFErZ1M%n`w61rqzcw8hjID&mIEzj!x2quS@LmW|9 zXxVx|m%NM4dGeQqxo08k09VrC9aH?f|3Yu`Z33?fHOmQPjTsN#1 zwD?HUF)ykJQEnI(Nx6h#V&aIEAQTm%3ZnKhgd98}*G=WAjp-xC%>@>9SJTxpDad6w zxRd0V>O33lMC2%5UKz7T5*Gz%1Uo%}sxhxTTm-9ZZ$lkR=Y!XEo}q%g>}_ANS>giG zc=QH`GaTh~oz9w$BC`<=fCnZ*SyP`G9CPcCn7pigh$JoM z;0NEse*C^IWjbY}%zTCe{G6)+md7w;Jv`JLRjIg#UOdcNgj3@MqruD+K^7TkWI8^K zp4fr&W;6~7%_40A(-37k%i-*Tz@`^?gLE^Qa>x${U@QjBC!FA=J@AlE=!VlgLbu=X z;9x&%bLqKWVOfY%l8F;KhDmv?^_xwbojRpvg!Z9Rp-Esiw;>&(?S=yr@FlQE9*g3G zp%?8~CFY_aTFx>g@(ik77$(rX)7coIL8oJA&jNH2?x}a7uNFDRf<6SBqoG~Yyh?J7 z%}oYnh8k(L3`h8*j5HDz9L1kQp;Dw&ISh|YNYOyy97*yHM{^c`Vqmke>jIMhfxdtN^pKSxpNW;&2u`QhP#^ zF<1)yQ~{)WReT0-@D)Cj7qw0~7c*&zBMe6J*%`$)g)`I4eN%p&G1RKm%=JLy#lnPW z5#sLp5-6Nk92{~F_b`x~B_5cEjyxc+IeCymRRg>`kTvi-6}JP4bCMDL5EScuNL_|0 zw-t8WezBXKQXXk~CAESuq=!gbz!8q~vr=88vusg^G<-?S(gBUknpdvk@b{U*7Ea8; z8@Y8lu!~XcVsX)>ZD5K|_$#>+{>jT>hcjNd%boboE#T!A0s)gdy8w_)hLz-fZS6*Uq5=_fv=L96Crt&TjDgWCE3R;4 zwhoh0sX+#hHKu1rVQc9BFX>{hp#MKVT(qM;`8z({aeMD){{a1e{5@A1b%+Lbw8tXJ};GRyU_n1`u{`!fA`c# z88eFrAOeU0B7g`W0*C-2fCwN0hyWsh2)s`aK>z>mlY+!-AOeU0B7g`W0*C-2fCwN0 zhyWsh2p|G`Ab{ilJ;1>rL;w*$1P}p401-e05CKF05kLeG0Yu<^f&e}LKY5@wHvLbJ z`Nz&pe0$=bANc+QwFA`y)%QsTF&l3a0*kwcCT2FymB##5>Z?m`bzfYdr{ z%qh~m5IF-AW^HgseS^&Il^zG&jKL|Si}0%~^n7r-*0Y;!F2d=9r_xq$GYQ!%Z_G!| z-H{cg>-^3LkcQEYzzRN{;4q#IE~JHW`}&|Bm4$W(+&6c2gzIESn9CRLg24RUL$S;c zmTX$Kmz7P+5spU_f2&n2cpjX+F;~6dn>VX?pmWq0^qu32^(q@rM9s>l#F6F3dYF|P zQL}QHGx$-{n=0R|=p-O3eP5~WCn}MZo&*e+o~g9+v%3c-W}Y}n%~FvqqM6>5>F?*h zAnleWXHK3hedPkPIFv{~&t~lkB^8Z`HaOyJHhGmroC~E{y=L`q^x< z>v=Zi^wY){Ui{w4nbW6BU)f>CCLNakzMeHc=^!*hcea`GJ8pzP_dI{=&SquLjv!(8 z2+ay;mr^~zcc7P+n4fpMUHW(SGE14EQD*G5O2_|)N*^DawoiQX@G5+{M}NNj)S+V= z8#7}+HLa=}8}66Q@bM>KVULDo$zMJlkuGzR>zQKRP$W@P6kW6ksf(KGN}geQmZZ6+ zt!PHYaz(YSn4+esifAjgE$XJL*p6g5wozYnn||v>cAE_KdD*Q-H}lZzWkN!zgW!!a zA%zWEhUE6!4Y-(%-Mg4D!u@D)B~ONt+-id({|w1*h9uTo3a=c?q=mV}%q1p8U1*VCG61y;iKuZ@U6PQmXp5%nU zV@OC`enR5X?Y0j?X)Y&zNB=924+%Ur>=cqh_9G%CLqx0x;blY`B>Q0xr zd(VZ1?+gu&Zp9Me;wKo^Q=Q7aAKtYiay_KC+;>SY$x{5BWG1F0*@!7T1LPKQ;QmBt z=VI+mQ4+2jcB5~@B_|12LgDd|gvMh`(elzVk%q4_QeGY-<^?ihp+xYxOe-X5g8wAA zIjjm3G}lRwIaA8e9k|0Npcl*~tRYF{Vv`VBOL-kl$kYrJkRv&9{kR!)u6NsTUqK9U zLg0B#NIa(riK-Qw&;XynTvopYw_08wr1R7#tUQSciPsM*mx9y+`v+jq>4wx}w;Lp3 zFdE?H&qfA|bi&Z@?Zo-Y5~GB?6#EF4R6)|KN~J2RF-g`1S*yyGs&1Ke$1_yV5=o`5 z(ALTjZ9_3c*E9%mEyt=z^-*}fbnxJ@jWL;gt*NSTw@`&ZOXnG)WSf#GE2bfurlyLD zt?8~@QCvk=VK@$#f!Ym$qw_TQdxvY98tjHFKtu@O772F$!yxaALaJp9ThBBlF5omR zF5xsS)naAHQHzzP#ig01#l@MX#g-yX<1LnuZrkEyr<%Yd-6>*{@DwqV@-&?%Jw;5? zo+2iRPZ5*Ur--TKhwWyZ^6)81boiX4H++t(h7h&asnq0~UBD+HC2~{5M0kpr!~pN) znqDY}6fKdNrY1tu)MBZTc@YNM-!vtOCTVVyND^8SMckyu(8kY+q?nS(i7A6plCsS; zXR-JkIf*3~kVKXXNaD-|q#I))-rjCP>vWgZElu2AT-nJW_ek&3l*!l-AxC?4eub0x4h{f zT6L3(n#fPLy=nez<13_E%tQ*e^wmT^Q~e$edeS+HpcHbg88m28C2q~a29A+-Ck`Cl z7<X@{tlGWah;hO-Q|B8CnI}K;4sVQC91=XcD5r08#Q>-GY=@T5uCCs2$z_ zW)qQ_O^4nC$F)3S@}{OPk4+qUY3%U-JvDRarNjTp)VHU8tMuWipPc&1u|I@=?~@;S z_r%0Z?bNVoTdLEWRLwVK{`K7Fp4>e)Idke%=?AZ}8CyE4-lRIuznsa=)pGjKiCVDs zz|;}0tu9?%Tzx^fTzf%SURf8GH?CY+5Hjmnblp0#!I#oQwGZ9vTa;`*i(!SW>*80jjdmzQ(RMhUG8DIDp}$mDSqP zQ_D<4=Mt*|lIMlh+NIiRZTUiNEoIxw;ktK5(EJyk-JP7AnV&CxWs6lIoVJI zwlZY{3u_c-q4|AR?c?y_x@y>@vFAn(dTZVlx?Ggr#xZ2QGVE~NosAlq%8_gniPy0A zwSgo`*9bc~%2BiHz$to;j#Lyg~l$2B01UXB@~W9)vIL~%a6vK zF%@rdppfZT?MGHd@z}7Bs}wa8@z833?e28Zr^b(s9XaslrOAIh`J>~{On&!)KOa9i zb>{e=9R1kQkB$A#(Jvi+;rQ+2x3BJ=oSZpxru4h7B{lyBU>dp}d-&o`Nd0F2RELGtZLR!5S6 zyR0OmShXbb(Ty8vIh~z$cvfJPc_D}crYY{utjU`;hVP^*jb9!+61KhS?gJAuPdz%U z9ZgCD8egtCM~{AJY+`cejbpphq46PMs~WFj@kOestYUHcUDGQUV)h&H*5N?i4%1@cq+F*=P{`oXx5jCEYdcxvxf2Zg-zI()`3%E+2@JRKC}BETg~`l zI9|>MXP?Hi1~?n|PMhLA%6>m874Ea#LqBeAlMdPF(xt{>D*UE9ii5+nyJ@m-{QvEp zO>Er86@a;ty!x>vrij~(1`Lg}+O3)4(-iw=obqf8Hd8kqZ1x7Ll~-?#hW# z^sr9rAcwSQF9mw&p+JG&a>yySHkYFJ07*{)nnPQ(Kzrz+KmxRH$mPtCyDNaA&1N1doK>53p`7^L4FcWnoy&!4Zs<@`O! z1@-UBl={A+zOVi(z4^sINB{{S0VIF~kN^@u0!RP}AOR$R1dzZNg23TvQd!>!H{qqIWG&km%qjAFhF@$=Kg&>C^o#K4*~JJ_V` zJo;dHrw~n+mU2yJAMKyqH#nYVM0m541(2t?tk?47m{|%;wx||eF0GyDbsh?No6`mM z!ftzuMx?Re%yGeBfgeHs>O6dNoJP`x#2hrRWk)T@$r3_*9fTF_5T>+i-h?|euIQG9 z^;92J4yPw=`_VzYT6_8-h-EhiU)w3Foks{lM`zjK=O9bWvDq3Sl{f6Pr?V!krRk5O z^78$~zFuCvXPuoHmJP(r+&vGcsO%Py!*8PyExBWaQTp$I0T?cMbEU zX8>H!cGcVUQglc3i`yA>yYOe_?%IVD&}FKFW7YJk^zh)mYVEOOlX_8p%-8h%(@8S8 zw^}=MbkY{XC~Jt>&7!mGub&?rnXMf?TDjg%d)ZJv{CB+A$A;2d-Ng#|5ul|Ih~6DH zaPIpaqz~@d@eVCd>%<1GPWK)~nSNxKBvAU`fgR1}$;X8oKL@I1{i1eyr7}2@))(C# zd~>#T`gp$Bj$T`1H;HTPVt5|%!40dtxWk@z{^HqlFVrEo)PxO|>rY;U{O)w1Q?cPk0eP|TBIFpw<`R?hh3Zd5ArL$wwO&3*ZIb(X)mh!Kl(-~t42@WZVG}Qo17ewxmN;(WX;vf?U2!JbXm4I- z@YN(-K$A;cS8Vnk_On zv&ATPC0pcP z$?DPomeRUxn~AHppgbs?on))qfzsymOek%?z154meZu>TAbXpyfm;>%QSVlktT(P-+Wj6$hMCR>n<&Rf&d2V06B|8m1L6~%AFq2?tH z(R^1Uy5Ty+buE(^zGX*x!-x#q+yVf!-dUZ|dED>SlkN5OI-G2MAX4u@O|p9b<+who zdzqJe%i)NU`~Zux{0NIOVZc|pj%7leZQ*|?eA&_E^4bo}K`XUp8*Y&kzQa(%&@@~{gqxQN>o zgxt6wZ^N<%Lk2SLVdic)S=l#aJLyu8#$#$Qpwcf*HUz7sCvyU2&s9BsbLE#(q! zCzp&`;m5W#oaN?AV!oHs3cj<_3ckfRBtRESUM>I;~F|5xASJt## zAT%l$3C+nxydfh)A%tS&K_+}GEC{(hL36|+L@4l!)KD>%aSui5h|yGcyw!9NhZ8H_ zfT0%Bz8A+`aYa7xj>_1`E3TraE^8P8@_v8mq5Dgn$ zFO6oJ-P9Vz0WK8{s#6Os<%s_kItb~OWrv>MAZ{aR5Z#SJ;_8irn2F<=iEkyo2BFDN z$gfKWp7r(yM1Dr2V~-eWzIs{Kk^3s(NbT}D>PXP=9IF9ipq=PG(apdoZXBD$474P& zJ@CY{I2YFhl46=a$}8mflt0UiyoAP`$2xs9Nfa z`|be%HW&#Y0VIF~kN^@u0!RP}AOR$R1nx`%C#PE#&DAZ#vI1g;c0{an!q(RV5+;#m zx=~~YPBcdqtw^)nzz&G#c%bOJ7V(oXB3|HWq3!4m*RW@)qT^|?83rDK&0}H(ZUSG> z1Nfu64a?W#hLM<6su|MvyqVuSL4)CkIkRiK*lnswnfM ze3~lCJk6M*`Tt~|HqiM0sil*OdKmuT4-!BENB{{S0VIF~kN^@u0!RP}?0W)(_SAf< z)jA?u$quI1Jufi>VmpRQ^eE7X>qkEEwZzgx*LQuVA@fC+=8(J+mnQhdns~Zh*Nvv( zHZ@1)?KBy`25qR?cLQye*{{rZ@m<^cIo1E0Qe#Dp)pz!NCr1EC00|%gB!C2v01`j~ zNB{{S0VIF~J_~^hGp$xdCSF|T>BQ6%U`?vFpUiWK#kv0}b93KQ)ZZ^&T)g+tZ|1)> z4~uhu_$xO@r@&U|`+^udXpYQaHizWy2w zcwN)kNBep(crY!(_SW5L5jM7leXt5GOVj?OR?0=#>hQCBA+|9^9J6AU5Je8V?D8^d zV>GGr<4U%TQH|oQ_0H8eTCN8)^w-}USCnwHirN@USy*}-EDc>8XNl4ZI{nA8q(oV@ zxG}b}2D&vK#{6 zbgxd<=xxPu`0}OBrTDe}WUZ_k>2qhEdF=5s&zv;0l~F(^VAZAXJn`i7C&szT0*GlL zy-Dp!I_uDzT3b(bI~!YlDAk-Q77EVM9(_Ya=eB5in3MD*SSc7BmVmSudR zRGJXWGD5!=ic%KweC9J`g_rl|IygHs->PNp zFq}|}+=fj8TX!IZcTAIc+a|GLel)TgVdQHrZAT#~s@pW(ro~!D-@G1*WSSu}^D3hQ zo$il$Gh)v(yijWpErHZ^R_F&L2m=dJ$w3Ib={5Ag*Jv}096j^7nC>-ghHl=|j z@W8NF>AKNyLN6gsWHgB7nl|xuGbB#zdrpwJh9BB#uhPYK*pRcfX%vpoP;^V%@e~9G zJglT=hwWH)+|XmWbaRl8awmr`~8z=tudcXdBr~68eKHw@gNfYB3 z#CQ9B!*UvssK5*$b^;ErL43pap~FT%NAp7831oVYjLw33U71lWMSWX+G0psUeSci& zu$4#v2_OL^fCP{L5uW1^qj?HGN#xdZUGOozzEc~-lfqrn*yw&}ny$SUh<0zl_8Q0o0fz`xG(;$S2 z*Tf@nBQWS(kVAaOa7g4jaU6M}*U+Tqg05Anyc5XWlgjk0n>`V9tEo3u9Z24-L1ksl zBW$XY&$wo!QcW3GWOSg?HH6L%F9tHV+cHLPmVvD`HE-3{Mj7zw+KfS}DdUQa&b7)6 z?*}q-u`*|uOfvKd7X!myZP>2Z4@y(L?6P6d`TAYY1sQj*Ri-%unJ-B)MsGZsU3PTS z%yp}}Rbo(>8x}P8jseui%g+Uweju~sn>ZJ;OTFH-omIV|?cNWlri?2xI@c;tnlEP{ z(+^~f-UNem0MPBG?ZVALiNTnDkZQ`fBBQgg^h0HK@#o6YpVWAXs5SMS#h)*J1Va2l z0!RP}AOR$R1dsp{Kmter2_S*bmq3;)PU1RZ1j|60_O-uVGa}=`te=@$wSo=SBXSHbn literal 0 HcmV?d00001 diff --git a/opa/data/data.json b/opa/data/data.json new file mode 100644 index 00000000000..e3e063b35ad --- /dev/null +++ b/opa/data/data.json @@ -0,0 +1,139 @@ +{ + "policies": [ + { + "description": "", + "resource": { + "id": "Chatflows", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Agentflows", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Executions", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Marketplaces", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Assistants", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["Manager"] + }, + { + "description": "", + "resource": { + "id": "Tools", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Credentials", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Variables", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + + { + "description": "", + "resource": { + "id": "API Keys", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "Document Stores", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + + + { + "description": "", + "resource": { + "id": "Roles", + "type": "task" + }, + "action":{ + "type":"view" + }, + "roles": ["admin"] + }, + { + "description": "", + "resource": { + "id": "1234", + "type": "task" + }, + "action":{ + "type":"read" + }, + "roles": ["admin1"] + } + ] +} \ No newline at end of file diff --git a/opa/policy.rego b/opa/policy.rego new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opa/policy/policy.rego b/opa/policy/policy.rego new file mode 100644 index 00000000000..e3989373f32 --- /dev/null +++ b/opa/policy/policy.rego @@ -0,0 +1,29 @@ +#User Access - Return true/false +package flow.rbac.useraccess +import rego.v1 + +default hasAccess := true + + +hasAccess if { + #print("Input document:", input) + print("vasu1") + print("Input document:", input) + some policy in data.policies + print("vasu2") + resource_matched(policy, input.resource, input.action) + print("vasu3") + user_role_matched(policy, input.user.roles) + print("vasu4") + +} + +resource_matched(policy, resource, action) if{ + policy.resource.id == resource.id + policy.action.type == action.type +} + +user_role_matched(policy, userRoles) if { + object.subset(sort(userRoles), sort(policy.roles)) +} + diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 88162738cdb..b2213e92ef2 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -203,28 +203,37 @@ export class App { const URL_CASE_SENSITIVE_REGEX: RegExp = /\/api\/v1\// await initializeJwtCookieMiddleware(this.app, this.identityManager) - + logger.info(`Inside config:`) this.app.use(async (req, res, next) => { // Step 1: Check if the req path contains /api/v1 regardless of case if (URL_CASE_INSENSITIVE_REGEX.test(req.path)) { + logger.info(`Inside config2:`) // Step 2: Check if the req path is casesensitive if (URL_CASE_SENSITIVE_REGEX.test(req.path)) { + logger.info(`Inside config3:`) // Step 3: Check if the req path is in the whitelist const isWhitelisted = whitelistURLs.some((url) => req.path.startsWith(url)) if (isWhitelisted) { + logger.info(`Inside config4:`) next() } else if (req.headers['x-request-from'] === 'internal') { + logger.info(`Inside config5:`) verifyToken(req, res, next) } else { + logger.info(`Inside config6:`) // Only check license validity for non-open-source platforms if (this.identityManager.getPlatformType() !== Platform.OPEN_SOURCE) { + logger.info(`Inside config7:`) if (!this.identityManager.isLicenseValid()) { + logger.info(`Inside config8:`) return res.status(401).json({ error: 'Unauthorized Access' }) } } const { isValid, workspaceId: apiKeyWorkSpaceId } = await validateAPIKey(req) + logger.info(`Inside config9:`) if (!isValid) { + logger.info(`Inside config10:`) return res.status(401).json({ error: 'Unauthorized Access' }) } @@ -232,7 +241,9 @@ export class App { const workspace = await this.AppDataSource.getRepository(Workspace).findOne({ where: { id: apiKeyWorkSpaceId } }) + logger.info(`Inside config11:`) if (!workspace) { + logger.info(`Inside config12:`) return res.status(401).json({ error: 'Unauthorized Access' }) } @@ -240,7 +251,9 @@ export class App { const ownerRole = await this.AppDataSource.getRepository(Role).findOne({ where: { name: GeneralRole.OWNER, organizationId: IsNull() } }) + logger.info(`Inside config13:`) if (!ownerRole) { + logger.info(`Inside config14:`) return res.status(401).json({ error: 'Unauthorized Access' }) } @@ -249,14 +262,17 @@ export class App { const org = await this.AppDataSource.getRepository(Organization).findOne({ where: { id: activeOrganizationId } }) + logger.info(`Inside config15:`) if (!org) { + logger.info(`Inside config16:`) return res.status(401).json({ error: 'Unauthorized Access' }) } const subscriptionId = org.subscriptionId as string const customerId = org.customerId as string const features = await this.identityManager.getFeaturesByPlan(subscriptionId) + logger.info(`Inside config17:`) const productId = await this.identityManager.getProductIdFromSubscription(subscriptionId) - + logger.info(`Inside config18:`) // @ts-ignore req.user = { permissions: [...JSON.parse(ownerRole.permissions)], diff --git a/packages/server/src/services/domainValidation/index.ts b/packages/server/src/services/domainValidation/index.ts new file mode 100644 index 00000000000..08f1870ffde --- /dev/null +++ b/packages/server/src/services/domainValidation/index.ts @@ -0,0 +1,102 @@ +import chatflowsService from '../chatflows' +import logger from '../../utils/logger' + +export class DomainValidationService { + /** + * Validates if the origin is allowed for a specific chatflow + * @param chatflowId - The chatflow ID to validate against + * @param origin - The origin URL to validate + * @param workspaceId - Optional workspace ID for enterprise features + * @returns Promise - True if domain is allowed, false otherwise + */ + static async validateChatflowDomain(chatflowId: string, origin: string, workspaceId?: string): Promise { + try { + const chatflow = await chatflowsService.getChatflowById(chatflowId, workspaceId) + + if (!chatflow?.chatbotConfig) { + logger.info(`No chatbotConfig found for chatflow ${chatflowId}, allowing domain`) + return true + } + + const config = JSON.parse(chatflow.chatbotConfig) + + // If no allowed origins configured or first entry is empty, allow all + if (!config.allowedOrigins?.length || config.allowedOrigins[0] === '') { + logger.info(`No domain restrictions configured for chatflow ${chatflowId}`) + return true + } + + const originHost = new URL(origin).host + const isAllowed = config.allowedOrigins.some((domain: string) => { + try { + const allowedOrigin = new URL(domain).host + return originHost === allowedOrigin + } catch (error) { + logger.warn(`Invalid domain format in allowedOrigins: ${domain}`) + return false + } + }) + + logger.info(`Domain validation for ${origin} against chatflow ${chatflowId}: ${isAllowed}`) + return isAllowed + + } catch (error) { + logger.error(`Error validating domain for chatflow ${chatflowId}:`, error) + return false + } + } + + /** + * Extracts chatflow ID from prediction URL + * @param url - The request URL + * @returns string | null - The chatflow ID or null if not found + */ + static extractChatflowId(url: string): string | null { + try { + const urlParts = url.split('/') + const predictionIndex = urlParts.indexOf('prediction') + + if (predictionIndex !== -1 && urlParts.length > predictionIndex + 1) { + const chatflowId = urlParts[predictionIndex + 1] + // Remove query parameters if present + return chatflowId.split('?')[0] + } + + return null + } catch (error) { + logger.error('Error extracting chatflow ID from URL:', error) + return null + } + } + + /** + * Validates if a request is a prediction request + * @param url - The request URL + * @returns boolean - True if it's a prediction request + */ + static isPredictionRequest(url: string): boolean { + return url.includes('/prediction/') + } + + /** + * Get the custom error message for unauthorized origin + * @param chatflowId - The chatflow ID + * @param workspaceId - Optional workspace ID + * @returns Promise - Custom error message or default + */ + static async getUnauthorizedOriginError(chatflowId: string, workspaceId?: string): Promise { + try { + const chatflow = await chatflowsService.getChatflowById(chatflowId, workspaceId) + + if (chatflow?.chatbotConfig) { + const config = JSON.parse(chatflow.chatbotConfig) + return config.allowedOriginsError || 'This site is not allowed to access this chatbot' + } + + return 'This site is not allowed to access this chatbot' + } catch (error) { + logger.error(`Error getting unauthorized origin error for chatflow ${chatflowId}:`, error) + return 'This site is not allowed to access this chatbot' + } + } +} \ No newline at end of file diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index 96bbab573cd..d7a0fd97a51 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -1,5 +1,7 @@ import { Request, Response, NextFunction } from 'express' import sanitizeHtml from 'sanitize-html' +import logger from './logger' +import { DomainValidationService } from '../services/domainValidation' export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void { // decoding is necessary as the url is encoded by the browser @@ -25,17 +27,50 @@ export function getAllowedCorsOrigins(): string { } export function getCorsOptions(): any { - const corsOptions = { - origin: function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) { - const allowedOrigins = getAllowedCorsOrigins() - if (!origin || allowedOrigins == '*' || allowedOrigins.indexOf(origin) !== -1) { - callback(null, true) - } else { - callback(null, false) + return function (req: any, callback: (err: Error | null, options?: any) => void) { + const corsOptions = { + origin: async function (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) { + const allowedOrigins = getAllowedCorsOrigins() + const isPredictionRequest = DomainValidationService.isPredictionRequest(req.url) + + logger.info('allowedOrigins: ' + allowedOrigins) + logger.info('origin: ' + origin) + logger.info('req.url: ' + req.url) + logger.info('req.method: ' + req.method) + logger.info('isPredictionRequest: ' + isPredictionRequest) + logger.info('req.headers: ' + JSON.stringify(req.headers)) + logger.info('req.query: ' + JSON.stringify(req.query)) + logger.info('req.body: ' + JSON.stringify(req.body)) + + // First check global CORS origins + if (!origin || allowedOrigins == '*' || allowedOrigins.indexOf(origin) !== -1) { + + // Additional prediction-specific validation + if (isPredictionRequest) { + const chatflowId = DomainValidationService.extractChatflowId(req.url) + if (chatflowId && origin) { + const isAllowed = await DomainValidationService.validateChatflowDomain( + chatflowId, + origin, + req.user?.activeWorkspaceId + ) + logger.info(`Prediction domain validation result: ${isAllowed}`) + originCallback(null, isAllowed) + } else { + logger.info('No chatflow ID found in prediction URL or no origin header, allowing request') + originCallback(null, true) + } + } else { + originCallback(null, true) + } + } else { + logger.info('Global CORS validation failed') + originCallback(null, false) + } } } + callback(null, corsOptions) } - return corsOptions } export function getAllowedIframeOrigins(): string { From 153127b81d9392a726048666aa1a34055d0d645d Mon Sep 17 00:00:00 2001 From: Vasudevan Ramasamy Date: Tue, 30 Sep 2025 09:09:17 -0700 Subject: [PATCH 10/10] removed data and opa --- data/database.sqlite | Bin 385024 -> 0 bytes opa/data/data.json | 139 ----------------------------------------- opa/policy.rego | 0 opa/policy/policy.rego | 29 --------- 4 files changed, 168 deletions(-) delete mode 100644 data/database.sqlite delete mode 100644 opa/data/data.json delete mode 100644 opa/policy.rego delete mode 100644 opa/policy/policy.rego diff --git a/data/database.sqlite b/data/database.sqlite deleted file mode 100644 index 6a726cf955f28e4a6470af1049429286536eb1ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 385024 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 diff --git a/opa/data/data.json b/opa/data/data.json deleted file mode 100644 index e3e063b35ad..00000000000 --- a/opa/data/data.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "policies": [ - { - "description": "", - "resource": { - "id": "Chatflows", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Agentflows", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Executions", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Marketplaces", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Assistants", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["Manager"] - }, - { - "description": "", - "resource": { - "id": "Tools", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Credentials", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Variables", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - - { - "description": "", - "resource": { - "id": "API Keys", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "Document Stores", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - - - { - "description": "", - "resource": { - "id": "Roles", - "type": "task" - }, - "action":{ - "type":"view" - }, - "roles": ["admin"] - }, - { - "description": "", - "resource": { - "id": "1234", - "type": "task" - }, - "action":{ - "type":"read" - }, - "roles": ["admin1"] - } - ] -} \ No newline at end of file diff --git a/opa/policy.rego b/opa/policy.rego deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/opa/policy/policy.rego b/opa/policy/policy.rego deleted file mode 100644 index e3989373f32..00000000000 --- a/opa/policy/policy.rego +++ /dev/null @@ -1,29 +0,0 @@ -#User Access - Return true/false -package flow.rbac.useraccess -import rego.v1 - -default hasAccess := true - - -hasAccess if { - #print("Input document:", input) - print("vasu1") - print("Input document:", input) - some policy in data.policies - print("vasu2") - resource_matched(policy, input.resource, input.action) - print("vasu3") - user_role_matched(policy, input.user.roles) - print("vasu4") - -} - -resource_matched(policy, resource, action) if{ - policy.resource.id == resource.id - policy.action.type == action.type -} - -user_role_matched(policy, userRoles) if { - object.subset(sort(userRoles), sort(policy.roles)) -} -