diff --git a/packages/server/src/controllers/chat-messages/index.ts b/packages/server/src/controllers/chat-messages/index.ts index d926d9e9b32..beefe64226e 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, workspaceId) + const chatflow = await chatflowsService.getChatflowByIdForWorkspace(req.params.id, workspaceId) if (!chatflow) { return res.status(404).send('Chatflow not found') } diff --git a/packages/server/src/controllers/chatflows/index.ts b/packages/server/src/controllers/chatflows/index.ts index c77f293e4f4..df115b56084 100644 --- a/packages/server/src/controllers/chatflows/index.ts +++ b/packages/server/src/controllers/chatflows/index.ts @@ -268,7 +268,14 @@ const checkIfChatflowHasChanged = async (req: Request, res: Response, next: Next `Error: chatflowsController.checkIfChatflowHasChanged - lastUpdatedDateTime not provided!` ) } - const apiResponse = await chatflowsService.checkIfChatflowHasChanged(req.params.id, req.params.lastUpdatedDateTime) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + 'Error: chatflowsController.checkIfChatflowHasChanged - active workspace ID not found!' + ) + } + const apiResponse = await chatflowsService.checkIfChatflowHasChanged(req.params.id, req.params.lastUpdatedDateTime, 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 0e75a4d6d71..2410351eeba 100644 --- a/packages/server/src/controllers/documentstore/index.ts +++ b/packages/server/src/controllers/documentstore/index.ts @@ -429,6 +429,17 @@ const previewFileChunks = async (req: Request, res: Response, next: NextFunction } const subscriptionId = req.user?.activeOrganizationSubscriptionId || '' const body = req.body + if (body.storeId) { + const store = await getRunningExpressApp() + .AppDataSource.getRepository(DocumentStore) + .findOneBy({ + id: body.storeId as string, + workspaceId + }) + if (!store) { + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, 'Document store not found') + } + } body.preview = true const apiResponse = await documentStoreService.previewChunksMiddleware( body, diff --git a/packages/server/src/controllers/internal-predictions/index.ts b/packages/server/src/controllers/internal-predictions/index.ts index 418421a4386..2009c693bef 100644 --- a/packages/server/src/controllers/internal-predictions/index.ts +++ b/packages/server/src/controllers/internal-predictions/index.ts @@ -12,7 +12,7 @@ const createInternalPrediction = async (req: Request, res: Response, next: NextF try { const workspaceId = req.user?.activeWorkspaceId - const chatflow = await chatflowService.getChatflowById(req.params.id, workspaceId) + const chatflow = await chatflowService.getChatflowByIdForWorkspace(req.params.id, workspaceId) if (!chatflow) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${req.params.id} not found`) } diff --git a/packages/server/src/controllers/leads/index.ts b/packages/server/src/controllers/leads/index.ts index db403a02ab3..d7c4dafd05f 100644 --- a/packages/server/src/controllers/leads/index.ts +++ b/packages/server/src/controllers/leads/index.ts @@ -1,4 +1,5 @@ import { Request, Response, NextFunction } from 'express' +import chatflowsService from '../../services/chatflows' import leadsService from '../../services/leads' import { StatusCodes } from 'http-status-codes' import { InternalFlowiseError } from '../../errors/internalFlowiseError' @@ -11,7 +12,21 @@ const getAllLeadsForChatflow = async (req: Request, res: Response, next: NextFun `Error: leadsController.getAllLeadsForChatflow - id not provided!` ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: leadsController.getAllLeadsForChatflow - workspace ${workspaceId} not found!` + ) + } const chatflowid = req.params.id + const chatflow = await chatflowsService.getChatflowByIdForWorkspace(chatflowid, workspaceId) + if (!chatflow) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: leadsController.getAllLeadsForChatflow - chatflow ${chatflowid} not found in workspace ${workspaceId}` + ) + } const apiResponse = await leadsService.getAllLeads(chatflowid) return res.json(apiResponse) } catch (error) { diff --git a/packages/server/src/controllers/openai-assistants-vector-store/index.ts b/packages/server/src/controllers/openai-assistants-vector-store/index.ts index 5657834e98f..7ceeddebaad 100644 --- a/packages/server/src/controllers/openai-assistants-vector-store/index.ts +++ b/packages/server/src/controllers/openai-assistants-vector-store/index.ts @@ -18,7 +18,18 @@ const getAssistantVectorStore = async (req: Request, res: Response, next: NextFu `Error: openaiAssistantsVectorStoreController.getAssistantVectorStore - credential not provided!` ) } - const apiResponse = await openAIAssistantVectorStoreService.getAssistantVectorStore(req.query.credential as string, req.params.id) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.getAssistantVectorStore - workspace not found!` + ) + } + const apiResponse = await openAIAssistantVectorStoreService.getAssistantVectorStore( + req.query.credential as string, + req.params.id, + workspaceId + ) return res.json(apiResponse) } catch (error) { next(error) @@ -33,7 +44,14 @@ const listAssistantVectorStore = async (req: Request, res: Response, next: NextF `Error: openaiAssistantsVectorStoreController.listAssistantVectorStore - credential not provided!` ) } - const apiResponse = await openAIAssistantVectorStoreService.listAssistantVectorStore(req.query.credential as string) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.listAssistantVectorStore - workspace not found!` + ) + } + const apiResponse = await openAIAssistantVectorStoreService.listAssistantVectorStore(req.query.credential as string, workspaceId) return res.json(apiResponse) } catch (error) { next(error) @@ -54,7 +72,18 @@ const createAssistantVectorStore = async (req: Request, res: Response, next: Nex `Error: openaiAssistantsVectorStoreController.createAssistantVectorStore - credential not provided!` ) } - const apiResponse = await openAIAssistantVectorStoreService.createAssistantVectorStore(req.query.credential as string, req.body) + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.createAssistantVectorStore - workspace not found!` + ) + } + const apiResponse = await openAIAssistantVectorStoreService.createAssistantVectorStore( + req.query.credential as string, + req.body, + workspaceId + ) return res.json(apiResponse) } catch (error) { next(error) @@ -81,10 +110,18 @@ const updateAssistantVectorStore = async (req: Request, res: Response, next: Nex `Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - body not provided!` ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - workspace not found!` + ) + } const apiResponse = await openAIAssistantVectorStoreService.updateAssistantVectorStore( req.query.credential as string, req.params.id, - req.body + req.body, + workspaceId ) return res.json(apiResponse) } catch (error) { @@ -106,9 +143,17 @@ const deleteAssistantVectorStore = async (req: Request, res: Response, next: Nex `Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - credential not provided!` ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.deleteAssistantVectorStore - workspace not found!` + ) + } const apiResponse = await openAIAssistantVectorStoreService.deleteAssistantVectorStore( req.query.credential as string, - req.params.id as string + req.params.id as string, + workspaceId ) return res.json(apiResponse) } catch (error) { @@ -154,10 +199,18 @@ const uploadFilesToAssistantVectorStore = async (req: Request, res: Response, ne } } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - workspace not found!` + ) + } const apiResponse = await openAIAssistantVectorStoreService.uploadFilesToAssistantVectorStore( req.query.credential as string, req.params.id as string, - uploadFiles + uploadFiles, + workspaceId ) return res.json(apiResponse) } catch (error) { @@ -186,10 +239,18 @@ const deleteFilesFromAssistantVectorStore = async (req: Request, res: Response, ) } + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - workspace not found!` + ) + } const apiResponse = await openAIAssistantVectorStoreService.deleteFilesFromAssistantVectorStore( req.query.credential as string, req.params.id as string, - req.body.file_ids + req.body.file_ids, + workspaceId ) return res.json(apiResponse) } catch (error) { diff --git a/packages/server/src/controllers/upsert-history/index.ts b/packages/server/src/controllers/upsert-history/index.ts index 43f36d5bf20..4381ade1cf9 100644 --- a/packages/server/src/controllers/upsert-history/index.ts +++ b/packages/server/src/controllers/upsert-history/index.ts @@ -1,10 +1,28 @@ import { Request, Response, NextFunction } from 'express' +import { StatusCodes } from 'http-status-codes' +import { InternalFlowiseError } from '../../errors/internalFlowiseError' +import chatflowsService from '../../services/chatflows' import upsertHistoryService from '../../services/upsert-history' const getAllUpsertHistory = async (req: Request, res: Response, next: NextFunction) => { try { - const sortOrder = req.query?.order as string | undefined + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: upsertHistoryController.getAllUpsertHistory - workspace ${workspaceId} not found!` + ) + } const chatflowid = req.params?.id as string | undefined + if (!chatflowid) { + throw new InternalFlowiseError( + StatusCodes.BAD_REQUEST, + 'Error: upsertHistoryController.getAllUpsertHistory - chatflow id is required!' + ) + } + await chatflowsService.getChatflowById(chatflowid, workspaceId) + + const sortOrder = req.query?.order as string | undefined const startDate = req.query?.startDate as string | undefined const endDate = req.query?.endDate as string | undefined const apiResponse = await upsertHistoryService.getAllUpsertHistory(sortOrder, chatflowid, startDate, endDate) @@ -16,8 +34,15 @@ const getAllUpsertHistory = async (req: Request, res: Response, next: NextFuncti const patchDeleteUpsertHistory = async (req: Request, res: Response, next: NextFunction) => { try { + const workspaceId = req.user?.activeWorkspaceId + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + `Error: upsertHistoryController.patchDeleteUpsertHistory - workspace ${workspaceId} not found!` + ) + } const ids = req.body.ids ?? [] - const apiResponse = await upsertHistoryService.patchDeleteUpsertHistory(ids) + const apiResponse = await upsertHistoryService.patchDeleteUpsertHistory(ids, workspaceId) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/enterprise/controllers/login-method.controller.ts b/packages/server/src/enterprise/controllers/login-method.controller.ts index 888d4011ba3..bb574a87432 100644 --- a/packages/server/src/enterprise/controllers/login-method.controller.ts +++ b/packages/server/src/enterprise/controllers/login-method.controller.ts @@ -12,6 +12,7 @@ import AzureSSO from '../sso/AzureSSO' import GithubSSO from '../sso/GithubSSO' import GoogleSSO from '../sso/GoogleSSO' import { decrypt } from '../utils/encryption.util' +import { assertQueryOrganizationMatchesActiveOrg, getLoggedInUser } from '../utils/tenantRequestGuards' export class LoginMethodController { constructor() { @@ -37,6 +38,10 @@ export class LoginMethodController { public async create(req: Request, res: Response, next: NextFunction) { try { this.assertEnterprisePlatform() + + const user = getLoggedInUser(req) + assertQueryOrganizationMatchesActiveOrg(user, req.body.organizationId) + const loginMethodService = new LoginMethodService() const loginMethod = await loginMethodService.createLoginMethod(req.body) return res.status(StatusCodes.CREATED).json(loginMethod) @@ -86,10 +91,7 @@ export class LoginMethodController { let queryRunner try { this.assertEnterprisePlatform() - const user = (req as any).user - if (!user?.activeOrganizationId) { - throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) - } + const user = getLoggedInUser(req) queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const query = req.query as Partial @@ -135,6 +137,10 @@ export class LoginMethodController { public async update(req: Request, res: Response, next: NextFunction) { try { this.assertEnterprisePlatform() + + const user = getLoggedInUser(req) + assertQueryOrganizationMatchesActiveOrg(user, req.body.organizationId) + const loginMethodService = new LoginMethodService() const loginMethod = await loginMethodService.createOrUpdateConfig(req.body) if (loginMethod?.status === 'OK' && loginMethod?.organizationId) { diff --git a/packages/server/src/enterprise/controllers/organization-user.controller.ts b/packages/server/src/enterprise/controllers/organization-user.controller.ts index 466c6ee1fe5..c0a5288bf88 100644 --- a/packages/server/src/enterprise/controllers/organization-user.controller.ts +++ b/packages/server/src/enterprise/controllers/organization-user.controller.ts @@ -17,6 +17,7 @@ import { WorkspaceUser } from '../database/entities/workspace-user.entity' import { OrganizationUserService } from '../services/organization-user.service' import { RoleService } from '../services/role.service' import { WorkspaceService } from '../services/workspace.service' +import { assertQueryOrganizationMatchesActiveOrg, getLoggedInUser, userMayManageOrgUsers } from '../utils/tenantRequestGuards' export class OrganizationUserController { public async create(req: Request, res: Response, next: NextFunction) { @@ -35,11 +36,14 @@ export class OrganizationUserController { public async read(req: Request, res: Response, next: NextFunction) { let queryRunner try { + const user = getLoggedInUser(req) queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const query = req.query as OrganizationUserQuery const organizationUserservice = new OrganizationUserService() + assertQueryOrganizationMatchesActiveOrg(user, query.organizationId) + let organizationUser: | { organization: Organization @@ -58,15 +62,32 @@ export class OrganizationUserController { queryRunner ) } else if (query.organizationId && query.roleId) { + if (!userMayManageOrgUsers(user)) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } organizationUser = await organizationUserservice.readOrganizationUserByOrganizationIdRoleId( query.organizationId, query.roleId, queryRunner ) } else if (query.organizationId) { + if (!userMayManageOrgUsers(user)) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } organizationUser = await organizationUserservice.readOrganizationUserByOrganizationId(query.organizationId, queryRunner) } else if (query.userId) { - organizationUser = await organizationUserservice.readOrganizationUserByUserId(query.userId, queryRunner) + if (query.userId !== user.id && !userMayManageOrgUsers(user)) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + if (query.userId === user.id) { + organizationUser = await organizationUserservice.readOrganizationUserByUserId(query.userId, queryRunner) + } else { + organizationUser = await organizationUserservice.readOrganizationUserByOrganizationIdUserId( + user.activeOrganizationId, + query.userId, + queryRunner + ) + } } else { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE) } diff --git a/packages/server/src/enterprise/controllers/role.controller.ts b/packages/server/src/enterprise/controllers/role.controller.ts index d12b8657ab6..cdfb78889f5 100644 --- a/packages/server/src/enterprise/controllers/role.controller.ts +++ b/packages/server/src/enterprise/controllers/role.controller.ts @@ -2,12 +2,17 @@ import { NextFunction, Request, Response } from 'express' import { StatusCodes } from 'http-status-codes' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { Role } from '../database/entities/role.entity' -import { RoleService } from '../services/role.service' +import { RoleErrorMessage, RoleService } from '../services/role.service' import { InternalFlowiseError } from '../../errors/internalFlowiseError' +import { GeneralErrorMessage } from '../../utils/constants' +import { assertQueryOrganizationMatchesActiveOrg, getLoggedInUser } from '../utils/tenantRequestGuards' export class RoleController { public async create(req: Request, res: Response, next: NextFunction) { try { + const user = getLoggedInUser(req) + assertQueryOrganizationMatchesActiveOrg(user, req.body.organizationId) + const roleService = new RoleService() const newRole = await roleService.createRole(req.body) return res.status(StatusCodes.CREATED).json(newRole) @@ -19,6 +24,7 @@ export class RoleController { public async read(req: Request, res: Response, next: NextFunction) { let queryRunner try { + const user = getLoggedInUser(req) queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const query = req.query as Partial @@ -26,8 +32,20 @@ export class RoleController { let role: Role | Role[] | null | (Role & { userCount: number })[] if (query.id) { - role = await roleService.readRoleById(query.id, queryRunner) + const oneRole = await roleService.readRoleById(query.id, queryRunner) + if (!oneRole) { + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) + } + if (oneRole.organizationId != null) { + if (!user.activeOrganizationId || oneRole.organizationId !== user.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + } + role = oneRole } else if (query.organizationId) { + if (!user.activeOrganizationId || query.organizationId !== user.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } role = await roleService.readRoleByOrganizationId(query.organizationId, queryRunner) } else { role = await roleService.readRoleByGeneral(queryRunner) @@ -43,6 +61,9 @@ export class RoleController { public async update(req: Request, res: Response, next: NextFunction) { try { + const user = getLoggedInUser(req) + assertQueryOrganizationMatchesActiveOrg(user, req.body.organizationId) + const roleService = new RoleService() const role = await roleService.updateRole(req.body) return res.status(StatusCodes.OK).json(role) diff --git a/packages/server/src/enterprise/controllers/user.controller.ts b/packages/server/src/enterprise/controllers/user.controller.ts index a10d19f6c99..78d4e214a80 100644 --- a/packages/server/src/enterprise/controllers/user.controller.ts +++ b/packages/server/src/enterprise/controllers/user.controller.ts @@ -4,8 +4,10 @@ import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { GeneralErrorMessage } from '../../utils/constants' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { User } from '../database/entities/user.entity' +import { LoggedInUser } from '../Interface.Enterprise' import { AccountService } from '../services/account.service' import { UserErrorMessage, UserService } from '../services/user.service' +import { assertMayReadTargetUser } from '../utils/tenantRequestGuards' export class UserController { public async create(req: Request, res: Response, next: NextFunction) { @@ -23,16 +25,32 @@ export class UserController { try { queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() + + const sessionUser = req.user as LoggedInUser | undefined + if (!sessionUser) { + throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, UserErrorMessage.USER_NOT_FOUND) + } + const query = req.query as Partial const userService = new UserService() let user: User | null if (query.id) { + await assertMayReadTargetUser(sessionUser, query.id, queryRunner) user = await userService.readUserById(query.id, queryRunner) if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND) } else if (query.email) { - user = await userService.readUserByEmail(query.email, queryRunner) - if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND) + const emailLc = (typeof query.email === 'string' ? query.email : '').trim().toLowerCase() + const selfEmail = sessionUser.email?.trim().toLowerCase() + if (!selfEmail || emailLc !== selfEmail) { + const byEmail = await userService.readUserByEmail(query.email, queryRunner) + if (!byEmail) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND) + await assertMayReadTargetUser(sessionUser, byEmail.id, queryRunner) + user = byEmail + } else { + user = await userService.readUserByEmail(query.email, queryRunner) + if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND) + } } else { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE) } diff --git a/packages/server/src/enterprise/controllers/workspace-user.controller.ts b/packages/server/src/enterprise/controllers/workspace-user.controller.ts index beab8b1ffc9..226a0d78885 100644 --- a/packages/server/src/enterprise/controllers/workspace-user.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace-user.controller.ts @@ -6,6 +6,12 @@ import { GeneralErrorMessage } from '../../utils/constants' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { WorkspaceUser } from '../database/entities/workspace-user.entity' import { WorkspaceUserService } from '../services/workspace-user.service' +import { + assertQueryOrganizationMatchesActiveOrg, + assertWorkspaceIdAccessibleToUser, + getLoggedInUser, + userMayManageOrgUsers +} from '../utils/tenantRequestGuards' export class WorkspaceUserController { public async create(req: Request, res: Response, next: NextFunction) { @@ -21,6 +27,7 @@ export class WorkspaceUserController { public async read(req: Request, res: Response, next: NextFunction) { let queryRunner try { + const user = getLoggedInUser(req) queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const query = req.query as Partial @@ -28,14 +35,20 @@ export class WorkspaceUserController { let workspaceUser: any if (query.workspaceId && query.userId) { + await assertWorkspaceIdAccessibleToUser(user, query.workspaceId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceIdUserId( query.workspaceId, query.userId, queryRunner ) } else if (query.workspaceId) { + await assertWorkspaceIdAccessibleToUser(user, query.workspaceId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceId(query.workspaceId, queryRunner) } else if (query.organizationId && query.userId) { + assertQueryOrganizationMatchesActiveOrg(user, query.organizationId) + if (query.userId !== user.id && !userMayManageOrgUsers(user)) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( query.organizationId, query.userId, @@ -44,7 +57,10 @@ export class WorkspaceUserController { } else if (query.userId) { workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(query.userId, queryRunner) } else if (query.roleId) { - workspaceUser = await workspaceUserService.readWorkspaceUserByRoleId(query.roleId, queryRunner) + if (!userMayManageOrgUsers(user)) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + workspaceUser = await workspaceUserService.readWorkspaceUserByRoleId(query.roleId, queryRunner, user.activeOrganizationId) } else { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE) } diff --git a/packages/server/src/enterprise/controllers/workspace.controller.ts b/packages/server/src/enterprise/controllers/workspace.controller.ts index dc29f97a654..e29621eeba6 100644 --- a/packages/server/src/enterprise/controllers/workspace.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace.controller.ts @@ -15,6 +15,7 @@ import { RoleErrorMessage, RoleService } from '../services/role.service' import { UserErrorMessage, UserService } from '../services/user.service' import { WorkspaceUserErrorMessage, WorkspaceUserService } from '../services/workspace-user.service' import { WorkspaceErrorMessage, WorkspaceService } from '../services/workspace.service' +import { assertQueryOrganizationMatchesActiveOrg, assertWorkspaceIdAccessibleToUser, getLoggedInUser } from '../utils/tenantRequestGuards' export class WorkspaceController { public async create(req: Request, res: Response, next: NextFunction) { @@ -30,11 +31,14 @@ export class WorkspaceController { public async read(req: Request, res: Response, next: NextFunction) { let queryRunner try { + const user = getLoggedInUser(req) queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const query = req.query as Partial const workspaceService = new WorkspaceService() + assertQueryOrganizationMatchesActiveOrg(user, query.organizationId) + let workspace: | Workspace | null @@ -42,9 +46,15 @@ export class WorkspaceController { userCount: number })[] if (query.id) { + await assertWorkspaceIdAccessibleToUser(user, query.id, queryRunner) workspace = await workspaceService.readWorkspaceById(query.id, queryRunner) } else if (query.organizationId) { workspace = await workspaceService.readWorkspaceByOrganizationId(query.organizationId, queryRunner) + if (!user.isOrganizationAdmin && Array.isArray(workspace)) { + const allowed = new Set((user.assignedWorkspaces ?? []).map((w) => w.id)) + if (user.activeWorkspaceId) allowed.add(user.activeWorkspaceId) + workspace = workspace.filter((w) => allowed.has(w.id)) + } } else { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE) } diff --git a/packages/server/src/enterprise/services/workspace-user.service.ts b/packages/server/src/enterprise/services/workspace-user.service.ts index 2b4f1772676..cd1172afad2 100644 --- a/packages/server/src/enterprise/services/workspace-user.service.ts +++ b/packages/server/src/enterprise/services/workspace-user.service.ts @@ -161,18 +161,21 @@ export class WorkspaceUserService { })) } - public async readWorkspaceUserByRoleId(roleId: string | undefined, queryRunner: QueryRunner) { + public async readWorkspaceUserByRoleId(roleId: string | undefined, queryRunner: QueryRunner, organizationId?: string | undefined) { const role = await this.roleService.readRoleById(roleId, queryRunner) if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) const ownerRole = await this.roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner) - const workspaceUsers = await queryRunner.manager + const qb = queryRunner.manager .createQueryBuilder(WorkspaceUser, 'workspaceUser') .innerJoinAndSelect('workspaceUser.workspace', 'workspace') .innerJoinAndSelect('workspaceUser.user', 'user') .innerJoinAndSelect('workspaceUser.role', 'role') .where('workspaceUser.roleId = :roleId', { roleId }) - .getMany() + if (organizationId) { + qb.andWhere('workspace.organizationId = :organizationId', { organizationId }) + } + const workspaceUsers = await qb.getMany() return workspaceUsers.map((workspaceUser) => { delete workspaceUser.user.credential diff --git a/packages/server/src/enterprise/utils/tenantRequestGuards.ts b/packages/server/src/enterprise/utils/tenantRequestGuards.ts new file mode 100644 index 00000000000..f9f18ab3008 --- /dev/null +++ b/packages/server/src/enterprise/utils/tenantRequestGuards.ts @@ -0,0 +1,93 @@ +import { Request } from 'express' +import { StatusCodes } from 'http-status-codes' +import { QueryRunner } from 'typeorm' +import { InternalFlowiseError } from '../../errors/internalFlowiseError' +import { GeneralErrorMessage } from '../../utils/constants' +import { Workspace } from '../database/entities/workspace.entity' +import { LoggedInUser } from '../Interface.Enterprise' +import { OrganizationUserService } from '../services/organization-user.service' + +export function getLoggedInUser(req: Request): LoggedInUser { + const user = req.user as LoggedInUser | undefined + if (!user?.id || !user?.activeOrganizationId || !user?.activeWorkspaceId) { + throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + } + return user +} + +/** + * Active workspace for tenant-scoped data access. + * Interactive sessions use {@link getLoggedInUser} (requires `req.user.id`). + * API key auth sets `activeWorkspaceId` / `activeOrganizationId` on `req.user` but not `id`. + */ +export function getActiveWorkspaceIdForRequest(req: Request): string { + const user = req.user as Partial | undefined + if (user?.id) { + return getLoggedInUser(req).activeWorkspaceId + } + if (!user?.activeWorkspaceId || !user?.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + } + return user.activeWorkspaceId +} + +/** When a query supplies organizationId, it must match the caller's active organization. */ +export function assertQueryOrganizationMatchesActiveOrg(user: LoggedInUser, organizationId: string | undefined): void { + if (organizationId === undefined || organizationId === '') return + if (organizationId !== user.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } +} + +/** + * Ensures the user may access data for this workspace: same as active workspace, listed in assigned workspaces, + * or org admin for a workspace that belongs to their active organization. + */ +export async function assertWorkspaceIdAccessibleToUser( + user: LoggedInUser, + workspaceId: string | undefined, + queryRunner: QueryRunner +): Promise { + if (workspaceId === undefined || workspaceId === '') return + + if (workspaceId === user.activeWorkspaceId) return + + if (user.assignedWorkspaces?.some((w) => w.id === workspaceId)) return + + if (user.isOrganizationAdmin) { + const workspace = await queryRunner.manager.findOneBy(Workspace, { id: workspaceId }) + if (!workspace || workspace.organizationId !== user.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + return + } + + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) +} + +export function userMayManageOrgUsers(user: LoggedInUser): boolean { + return user.isOrganizationAdmin === true || (user.permissions?.includes('users:manage') ?? false) +} + +/** Allows reading a user profile when it is self, or when the caller manages org users and the target belongs to the active org. */ +export async function assertMayReadTargetUser(sessionUser: LoggedInUser, targetUserId: string, queryRunner: QueryRunner): Promise { + if (sessionUser.id && targetUserId === sessionUser.id) return + if (sessionUser.id !== targetUserId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + if (!sessionUser.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + if (!userMayManageOrgUsers(sessionUser)) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } + const organizationUserService = new OrganizationUserService() + const { organizationUser } = await organizationUserService.readOrganizationUserByOrganizationIdUserId( + sessionUser.activeOrganizationId, + targetUserId, + queryRunner + ) + if (!organizationUser) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } +} diff --git a/packages/server/src/routes/oauth2/index.ts b/packages/server/src/routes/oauth2/index.ts index b5c5f571b76..817e7d64c18 100644 --- a/packages/server/src/routes/oauth2/index.ts +++ b/packages/server/src/routes/oauth2/index.ts @@ -61,6 +61,8 @@ import axios from 'axios' import { Request, Response, NextFunction } from 'express' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { Credential } from '../../database/entities/Credential' +import { WorkspaceShared } from '../../enterprise/database/entities/EnterpriseEntities' +import { getActiveWorkspaceIdForRequest } from '../../enterprise/utils/tenantRequestGuards' import { decryptCredentialData, encryptCredentialData } from '../../utils' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { StatusCodes } from 'http-status-codes' @@ -72,20 +74,29 @@ const router = express.Router() router.post('/authorize/:credentialId', async (req: Request, res: Response, next: NextFunction) => { try { const { credentialId } = req.params + const workspaceId = getActiveWorkspaceIdForRequest(req) const appServer = getRunningExpressApp() const credentialRepository = appServer.AppDataSource.getRepository(Credential) - // Find credential by ID - const credential = await credentialRepository.findOneBy({ - id: credentialId + let credential = await credentialRepository.findOneBy({ + id: credentialId, + workspaceId }) if (!credential) { - return res.status(404).json({ - success: false, - message: 'Credential not found' + const share = await appServer.AppDataSource.getRepository(WorkspaceShared).findOneBy({ + workspaceId, + sharedItemId: credentialId, + itemType: 'credential' }) + if (share) { + credential = await credentialRepository.findOneBy({ id: credentialId }) + } + } + + if (!credential) { + return next(new InternalFlowiseError(StatusCodes.NOT_FOUND, 'Credential not found')) } // Decrypt the credential data to get OAuth configuration @@ -144,6 +155,9 @@ router.post('/authorize/:credentialId', async (req: Request, res: Response, next redirectUri: finalRedirectUri }) } catch (error) { + if (error instanceof InternalFlowiseError) { + return next(error) + } next( new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, diff --git a/packages/server/src/services/chatflows/index.ts b/packages/server/src/services/chatflows/index.ts index 697b757d6c7..1afed4d3c59 100644 --- a/packages/server/src/services/chatflows/index.ts +++ b/packages/server/src/services/chatflows/index.ts @@ -25,7 +25,8 @@ import { updateStorageUsage } from '../../utils/quotaUsage' export const enum ChatflowErrorMessage { INVALID_CHATFLOW_TYPE = 'Invalid Chatflow Type', - INVALID_CHATFLOW_ID = 'Invalid Chatflow ID' + INVALID_CHATFLOW_ID = 'Invalid Chatflow ID', + WORKSPACE_ID_REQUIRED = 'Workspace ID is required' } export function validateChatflowType(type: ChatflowType | undefined) { @@ -276,6 +277,14 @@ const getChatflowById = async (chatflowId: string, workspaceId?: string): Promis } } +/** Resolves a chatflow only if it belongs to the given workspace; rejects when workspaceId is missing (prevents unscoped lookup). */ +const getChatflowByIdForWorkspace = async (chatflowId: string, workspaceId: string | undefined): Promise => { + if (!workspaceId) { + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, ChatflowErrorMessage.WORKSPACE_ID_REQUIRED) + } + return getChatflowById(chatflowId, workspaceId) +} + const saveChatflow = async ( newChatFlow: ChatFlow, orgId: string, @@ -442,20 +451,17 @@ const _checkAndUpdateDocumentStoreUsage = async (chatflow: ChatFlow, workspaceId } } -const checkIfChatflowHasChanged = async (chatflowId: string, lastUpdatedDateTime: string): Promise => { +const checkIfChatflowHasChanged = async (chatflowId: string, lastUpdatedDateTime: string, workspaceId: string): Promise => { try { - const appServer = getRunningExpressApp() - //** - const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: chatflowId - }) + const chatflow = await getChatflowByIdForWorkspace(chatflowId, workspaceId) if (!chatflow) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found`) } - // parse the lastUpdatedDateTime as a date and - //check if the updatedDate is the same as the lastUpdatedDateTime return { hasChanged: chatflow.updatedDate.toISOString() !== lastUpdatedDateTime } } catch (error) { + if (error instanceof InternalFlowiseError) { + throw error + } throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: chatflowsService.checkIfChatflowHasChanged - ${getErrorMessage(error)}` @@ -471,6 +477,7 @@ export default { getAllChatflowsCount, getChatflowByApiKey, getChatflowById, + getChatflowByIdForWorkspace, saveChatflow, updateChatflow, getSinglePublicChatbotConfig, diff --git a/packages/server/src/services/dataset/index.ts b/packages/server/src/services/dataset/index.ts index ca2e21b2904..8e8b99e0c5f 100644 --- a/packages/server/src/services/dataset/index.ts +++ b/packages/server/src/services/dataset/index.ts @@ -239,11 +239,18 @@ const deleteDataset = async (id: string, workspaceId: string) => { const appServer = getRunningExpressApp() const result = await appServer.AppDataSource.getRepository(Dataset).delete({ id: id, workspaceId: workspaceId }) - // delete all rows for this dataset + if ((result.affected ?? 0) === 0) { + // Same response whether the id is missing or belongs to another workspace (no enumeration). + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, 'Dataset not found') + } + await appServer.AppDataSource.getRepository(DatasetRow).delete({ datasetId: id }) return result } catch (error) { + if (error instanceof InternalFlowiseError) { + throw error + } throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: datasetService.deleteDataset - ${getErrorMessage(error)}`) } } diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts index ee145276d94..09551549261 100644 --- a/packages/server/src/services/documentstore/index.ts +++ b/packages/server/src/services/documentstore/index.ts @@ -613,7 +613,8 @@ const _normalizeFilePaths = async ( appDataSource: DataSource, data: IDocumentStoreLoaderForPreview, entity: DocumentStore | null, - orgId: string + orgId: string, + workspaceId?: string ) => { const keys = Object.getOwnPropertyNames(data.loaderConfig) let rehydrated = false @@ -628,8 +629,15 @@ const _normalizeFilePaths = async ( let documentStoreEntity: DocumentStore | null = entity if (input.startsWith('FILE-STORAGE::')) { if (!documentStoreEntity) { + if (!workspaceId) { + throw new InternalFlowiseError( + StatusCodes.PRECONDITION_FAILED, + 'workspaceId is required to resolve document store for FILE-STORAGE paths' + ) + } documentStoreEntity = await appDataSource.getRepository(DocumentStore).findOneBy({ - id: data.storeId + id: data.storeId, + workspaceId }) if (!documentStoreEntity) { throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${data.storeId} not found`) @@ -701,6 +709,9 @@ const previewChunksMiddleware = async ( return await previewChunks(executeData) } catch (error) { + if (error instanceof InternalFlowiseError) { + throw error + } throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: documentStoreServices.previewChunksMiddleware - ${getErrorMessage(error)}` @@ -720,7 +731,7 @@ export const previewChunks = async ({ appDataSource, componentNodes, data, orgId } } if (!data.rehydrated) { - await _normalizeFilePaths(appDataSource, data, null, orgId) + await _normalizeFilePaths(appDataSource, data, null, orgId, workspaceId) } let docs = await _splitIntoChunks(appDataSource, componentNodes, data, workspaceId) const totalChunks = docs.length @@ -733,6 +744,9 @@ export const previewChunks = async ({ appDataSource, componentNodes, data, orgId return { chunks: docs, totalChunks: totalChunks, previewChunkCount: data.previewChunkCount } } catch (error) { + if (error instanceof InternalFlowiseError) { + throw error + } throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: documentStoreServices.previewChunks - ${getErrorMessage(error)}` diff --git a/packages/server/src/services/evaluations/index.ts b/packages/server/src/services/evaluations/index.ts index 1acbfc3b894..4ae56d1a38b 100644 --- a/packages/server/src/services/evaluations/index.ts +++ b/packages/server/src/services/evaluations/index.ts @@ -453,11 +453,22 @@ const getAllEvaluations = async (workspaceId: string, page: number = -1, limit: const deleteEvaluation = async (id: string, activeWorkspaceId: string) => { try { const appServer = getRunningExpressApp() - await appServer.AppDataSource.getRepository(Evaluation).delete({ id: id }) + const evaluationRepo = appServer.AppDataSource.getRepository(Evaluation) + const existing = await evaluationRepo.findOneBy({ + id, + workspaceId: activeWorkspaceId + }) + if (!existing) { + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Evaluation ${id} not found`) + } await appServer.AppDataSource.getRepository(EvaluationRun).delete({ evaluationId: id }) - const results = await appServer.AppDataSource.getRepository(Evaluation).findBy(getWorkspaceSearchOptions(activeWorkspaceId)) + await evaluationRepo.delete({ id, workspaceId: activeWorkspaceId }) + const results = await evaluationRepo.findBy(getWorkspaceSearchOptions(activeWorkspaceId)) return results } catch (error) { + if (error instanceof InternalFlowiseError) { + throw error + } throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: EvalsService.deleteEvaluation - ${getErrorMessage(error)}` diff --git a/packages/server/src/services/openai-assistants-vector-store/index.ts b/packages/server/src/services/openai-assistants-vector-store/index.ts index 851ce86b5fd..0001d859288 100644 --- a/packages/server/src/services/openai-assistants-vector-store/index.ts +++ b/packages/server/src/services/openai-assistants-vector-store/index.ts @@ -1,21 +1,49 @@ import OpenAI from 'openai' import { StatusCodes } from 'http-status-codes' import { Credential } from '../../database/entities/Credential' +import { WorkspaceShared } from '../../enterprise/database/entities/EnterpriseEntities' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { getErrorMessage } from '../../errors/utils' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { decryptCredentialData } from '../../utils' import { getFileFromUpload, removeSpecificFileFromUpload } from 'flowise-components' -const getAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => { - try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId +const rethrowIfFlowiseError = (error: unknown): void => { + if (error instanceof InternalFlowiseError) { + throw error + } +} + +const resolveCredentialForWorkspace = async (credentialId: string, workspaceId: string | undefined): Promise => { + if (!workspaceId) { + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Workspace ID is required') + } + const appServer = getRunningExpressApp() + const credentialRepo = appServer.AppDataSource.getRepository(Credential) + + let credential = await credentialRepo.findOneBy({ + id: credentialId, + workspaceId + }) + if (!credential) { + const share = await appServer.AppDataSource.getRepository(WorkspaceShared).findOneBy({ + workspaceId, + sharedItemId: credentialId, + itemType: 'credential' }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) + if (share) { + credential = await credentialRepo.findOneBy({ id: credentialId }) } + } + if (credential) { + return credential + } + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, 'Credential not found') +} + +const getAssistantVectorStore = async (credentialId: string, vectorStoreId: string, workspaceId: string | undefined) => { + try { + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -27,6 +55,7 @@ const getAssistantVectorStore = async (credentialId: string, vectorStoreId: stri const dbResponse = await openai.vectorStores.retrieve(vectorStoreId) return dbResponse } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.getAssistantVectorStore - ${getErrorMessage(error)}` @@ -34,15 +63,9 @@ const getAssistantVectorStore = async (credentialId: string, vectorStoreId: stri } } -const listAssistantVectorStore = async (credentialId: string) => { +const listAssistantVectorStore = async (credentialId: string, workspaceId: string | undefined) => { try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) - } + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -54,6 +77,7 @@ const listAssistantVectorStore = async (credentialId: string) => { const dbResponse = await openai.vectorStores.list() return dbResponse.data } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.listAssistantVectorStore - ${getErrorMessage(error)}` @@ -61,15 +85,13 @@ const listAssistantVectorStore = async (credentialId: string) => { } } -const createAssistantVectorStore = async (credentialId: string, obj: OpenAI.VectorStores.VectorStoreCreateParams) => { +const createAssistantVectorStore = async ( + credentialId: string, + obj: OpenAI.VectorStores.VectorStoreCreateParams, + workspaceId: string | undefined +) => { try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) - } + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -81,6 +103,7 @@ const createAssistantVectorStore = async (credentialId: string, obj: OpenAI.Vect const dbResponse = await openai.vectorStores.create(obj) return dbResponse } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.createAssistantVectorStore - ${getErrorMessage(error)}` @@ -91,16 +114,11 @@ const createAssistantVectorStore = async (credentialId: string, obj: OpenAI.Vect const updateAssistantVectorStore = async ( credentialId: string, vectorStoreId: string, - obj: OpenAI.VectorStores.VectorStoreUpdateParams + obj: OpenAI.VectorStores.VectorStoreUpdateParams, + workspaceId: string | undefined ) => { try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) - } + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -121,6 +139,7 @@ const updateAssistantVectorStore = async ( } return dbResponse } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.updateAssistantVectorStore - ${getErrorMessage(error)}` @@ -128,15 +147,9 @@ const updateAssistantVectorStore = async ( } } -const deleteAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => { +const deleteAssistantVectorStore = async (credentialId: string, vectorStoreId: string, workspaceId: string | undefined) => { try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) - } + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -148,6 +161,7 @@ const deleteAssistantVectorStore = async (credentialId: string, vectorStoreId: s const dbResponse = await openai.vectorStores.delete(vectorStoreId) return dbResponse } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.deleteAssistantVectorStore - ${getErrorMessage(error)}` @@ -158,16 +172,11 @@ const deleteAssistantVectorStore = async (credentialId: string, vectorStoreId: s const uploadFilesToAssistantVectorStore = async ( credentialId: string, vectorStoreId: string, - files: { filePath: string; fileName: string }[] + files: { filePath: string; fileName: string }[], + workspaceId: string | undefined ): Promise => { try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) - } + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -205,6 +214,7 @@ const uploadFilesToAssistantVectorStore = async ( 'Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - Upload cancelled!' ) } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - ${getErrorMessage(error)}` @@ -212,15 +222,14 @@ const uploadFilesToAssistantVectorStore = async ( } } -const deleteFilesFromAssistantVectorStore = async (credentialId: string, vectorStoreId: string, file_ids: string[]) => { +const deleteFilesFromAssistantVectorStore = async ( + credentialId: string, + vectorStoreId: string, + file_ids: string[], + workspaceId: string | undefined +) => { try { - const appServer = getRunningExpressApp() - const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) - if (!credential) { - throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`) - } + const credential = await resolveCredentialForWorkspace(credentialId, workspaceId) // Decrpyt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) const openAIApiKey = decryptedCredentialData['openAIApiKey'] @@ -241,6 +250,7 @@ const deleteFilesFromAssistantVectorStore = async (credentialId: string, vectorS return { deletedFileIds, count } } catch (error) { + rethrowIfFlowiseError(error) throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - ${getErrorMessage(error)}` diff --git a/packages/server/src/services/upsert-history/index.ts b/packages/server/src/services/upsert-history/index.ts index f606a7a6571..12f80e4db69 100644 --- a/packages/server/src/services/upsert-history/index.ts +++ b/packages/server/src/services/upsert-history/index.ts @@ -1,9 +1,10 @@ -import { MoreThanOrEqual, LessThanOrEqual, Between } from 'typeorm' +import { MoreThanOrEqual, LessThanOrEqual, Between, In } from 'typeorm' import { StatusCodes } from 'http-status-codes' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { UpsertHistory } from '../../database/entities/UpsertHistory' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { getErrorMessage } from '../../errors/utils' +import chatflowsService from '../chatflows' const getAllUpsertHistory = async ( sortOrder: string | undefined, @@ -50,12 +51,39 @@ const getAllUpsertHistory = async ( } } -const patchDeleteUpsertHistory = async (ids: string[] = []): Promise => { +const patchDeleteUpsertHistory = async (ids: string[] = [], workspaceId: string): Promise => { try { + const uniqueIds = [...new Set((ids ?? []).filter((id) => typeof id === 'string' && id.length > 0))] + if (uniqueIds.length === 0) { + throw new InternalFlowiseError( + StatusCodes.BAD_REQUEST, + 'Error: upsertHistoryServices.patchDeleteUpsertHistory - ids are required!' + ) + } + const appServer = getRunningExpressApp() - const dbResponse = await appServer.AppDataSource.getRepository(UpsertHistory).delete(ids) - return dbResponse + const repo = appServer.AppDataSource.getRepository(UpsertHistory) + const rows = await repo.find({ + where: { id: In(uniqueIds) }, + select: ['id', 'chatflowid'] + }) + if (rows.length !== uniqueIds.length) { + throw new InternalFlowiseError( + StatusCodes.NOT_FOUND, + 'Error: upsertHistoryServices.patchDeleteUpsertHistory - one or more upsert history records were not found!' + ) + } + + const chatflowIds = [...new Set(rows.map((r) => r.chatflowid))] + for (const chatflowId of chatflowIds) { + await chatflowsService.getChatflowById(chatflowId, workspaceId) + } + + return await repo.delete(uniqueIds) } catch (error) { + if (error instanceof InternalFlowiseError) { + throw error + } throw new InternalFlowiseError( StatusCodes.INTERNAL_SERVER_ERROR, `Error: upsertHistoryServices.patchDeleteUpsertHistory - ${getErrorMessage(error)}`