diff --git a/packages/server/src/enterprise/controllers/organization-user.controller.ts b/packages/server/src/enterprise/controllers/organization-user.controller.ts index c0a5288bf..d54558aa5 100644 --- a/packages/server/src/enterprise/controllers/organization-user.controller.ts +++ b/packages/server/src/enterprise/controllers/organization-user.controller.ts @@ -22,6 +22,11 @@ import { assertQueryOrganizationMatchesActiveOrg, getLoggedInUser, userMayManage export class OrganizationUserController { public async create(req: Request, res: Response, next: NextFunction) { try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + + req.body.organizationId = req.user.activeOrganizationId + req.body.createdBy = req.user.id + const organizationUserservice = new OrganizationUserService() const totalOrgUsers = await organizationUserservice.readOrgUsersCountByOrgId(req.body.organizationId) const subscriptionId = req.user?.activeOrganizationSubscriptionId || '' @@ -102,6 +107,11 @@ export class OrganizationUserController { public async update(req: Request, res: Response, next: NextFunction) { try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + + req.body.organizationId = req.user.activeOrganizationId + req.body.updatedBy = req.user.id + const organizationUserService = new OrganizationUserService() const organizationUser = await organizationUserService.updateOrganizationUser(req.body) return res.status(StatusCodes.OK).json(organizationUser) @@ -116,10 +126,16 @@ export class OrganizationUserController { queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() const currentPlatform = getRunningExpressApp().identityManager.getPlatformType() await queryRunner.connect() + + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + const query = req.query as Partial if (!query.organizationId) { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Organization ID is required') } + if (query.organizationId !== req.user.activeOrganizationId) { + throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + } if (!query.userId) { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'User ID is required') } @@ -138,7 +154,11 @@ export class OrganizationUserController { }) if (personalWorkspaces.length === 1) // delete personal workspace - await workspaceService.deleteWorkspaceById(queryRunner, personalWorkspaces[0].workspaceId) + await workspaceService.deleteWorkspaceById( + queryRunner, + personalWorkspaces[0].workspaceId, + req.user.activeOrganizationId + ) // remove user from other workspces organizationUser = await organizationUserService.deleteOrganizationUser(queryRunner, query.organizationId, query.userId) // soft delete user because they might workspace might created by them diff --git a/packages/server/src/enterprise/controllers/workspace-user.controller.ts b/packages/server/src/enterprise/controllers/workspace-user.controller.ts index b2c9945f7..e95f23356 100644 --- a/packages/server/src/enterprise/controllers/workspace-user.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace-user.controller.ts @@ -4,7 +4,7 @@ import { QueryRunner } from 'typeorm' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { GeneralErrorMessage } from '../../utils/constants' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' -import { WorkspaceUser } from '../database/entities/workspace-user.entity' +import { WorkspaceUser, WorkspaceUserStatus } from '../database/entities/workspace-user.entity' import { WorkspaceUserService } from '../services/workspace-user.service' import { assertMayReadTargetUser, @@ -17,8 +17,13 @@ import { export class WorkspaceUserController { public async create(req: Request, res: Response, next: NextFunction) { try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + + req.body.createdBy = req.user.id + req.body.status = WorkspaceUserStatus.INVITED + const workspaceUserService = new WorkspaceUserService() - const newWorkspaceUser = await workspaceUserService.createWorkspaceUser(req.body) + const newWorkspaceUser = await workspaceUserService.createWorkspaceUser(req.body, req.user.activeOrganizationId) return res.status(StatusCodes.CREATED).json(newWorkspaceUser) } catch (error) { next(error) @@ -92,10 +97,14 @@ export class WorkspaceUserController { public async update(req: Request, res: Response, next: NextFunction) { let queryRunner: QueryRunner | undefined try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + + req.body.updatedBy = req.user.id + queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const workspaceUserService = new WorkspaceUserService() - const workspaceUser = await workspaceUserService.updateWorkspaceUser(req.body, queryRunner) + const workspaceUser = await workspaceUserService.updateWorkspaceUser(req.body, queryRunner, req.user.activeOrganizationId) return res.status(StatusCodes.OK).json(workspaceUser) } catch (error) { if (queryRunner && queryRunner.isTransactionActive) await queryRunner.rollbackTransaction() @@ -107,10 +116,16 @@ export class WorkspaceUserController { public async delete(req: Request, res: Response, next: NextFunction) { try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + const query = req.query as Partial const workspaceUserService = new WorkspaceUserService() - const workspaceUser = await workspaceUserService.deleteWorkspaceUser(query.workspaceId, query.userId) + const workspaceUser = await workspaceUserService.deleteWorkspaceUser( + query.workspaceId, + query.userId, + req.user.activeOrganizationId + ) return res.status(StatusCodes.OK).json(workspaceUser) } catch (error) { next(error) diff --git a/packages/server/src/enterprise/controllers/workspace.controller.ts b/packages/server/src/enterprise/controllers/workspace.controller.ts index e29621eeb..4640d09bc 100644 --- a/packages/server/src/enterprise/controllers/workspace.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace.controller.ts @@ -20,6 +20,12 @@ import { assertQueryOrganizationMatchesActiveOrg, assertWorkspaceIdAccessibleToU export class WorkspaceController { public async create(req: Request, res: Response, next: NextFunction) { try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + + req.body.organizationId = req.user.activeOrganizationId + req.body.createdBy = req.user.id + req.body.existingWorkspaceId = req.body.existingWorkspaceId ? req.user.activeWorkspaceId : undefined + const workspaceUserService = new WorkspaceUserService() const newWorkspace = await workspaceUserService.createWorkspace(req.body) return res.status(StatusCodes.CREATED).json(newWorkspace) @@ -135,7 +141,7 @@ export class WorkspaceController { activeOrganizationSubscriptionId: subscriptionId, activeOrganizationCustomerId: customerId, activeOrganizationProductId: productId, - isOrganizationAdmin: workspaceUser.roleId === ownerRole.id, + isOrganizationAdmin: organizationUser.roleId === ownerRole.id, activeWorkspaceId: workspace.id, activeWorkspace: workspace.name, assignedWorkspaces, @@ -179,6 +185,11 @@ export class WorkspaceController { public async update(req: Request, res: Response, next: NextFunction) { try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + + req.body.organizationId = req.user.activeOrganizationId + req.body.updatedBy = req.user.id + const workspaceService = new WorkspaceService() const workspace = await workspaceService.updateWorkspace(req.body) return res.status(StatusCodes.OK).json(workspace) @@ -190,6 +201,8 @@ export class WorkspaceController { public async delete(req: Request, res: Response, next: NextFunction) { let queryRunner: QueryRunner | undefined try { + if (!req.user) throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED) + queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner() await queryRunner.connect() const workspaceId = req.params.id @@ -199,7 +212,7 @@ export class WorkspaceController { const workspaceService = new WorkspaceService() await queryRunner.startTransaction() - const workspace = await workspaceService.deleteWorkspaceById(queryRunner, workspaceId) + const workspace = await workspaceService.deleteWorkspaceById(queryRunner, workspaceId, req.user.activeOrganizationId) await queryRunner.commitTransaction() return res.status(StatusCodes.OK).json(workspace) diff --git a/packages/server/src/enterprise/middleware/passport/index.ts b/packages/server/src/enterprise/middleware/passport/index.ts index 88c700a4f..dd82bd5a1 100644 --- a/packages/server/src/enterprise/middleware/passport/index.ts +++ b/packages/server/src/enterprise/middleware/passport/index.ts @@ -143,7 +143,7 @@ export const initializeJwtCookieMiddleware = async (app: express.Application, id if (!organizationUser) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, OrganizationUserErrorMessage.ORGANIZATION_USER_NOT_FOUND) organizationUser.status = OrganizationUserStatus.ACTIVE - await workspaceUserService.updateWorkspaceUser(workspaceUser, queryRunner) + await workspaceUserService.updateWorkspaceUser(workspaceUser, queryRunner, organizationUser.organizationId) await organizationUserService.updateOrganizationUser(organizationUser) const workspaceUsers = await workspaceUserService.readWorkspaceUserByUserId(organizationUser.userId, queryRunner) @@ -180,7 +180,7 @@ export const initializeJwtCookieMiddleware = async (app: express.Application, id activeOrganizationSubscriptionId: subscriptionId, activeOrganizationCustomerId: customerId, activeOrganizationProductId: productId, - isOrganizationAdmin: workspaceUser.roleId === ownerRole.id, + isOrganizationAdmin: organizationUser.roleId === ownerRole.id, activeWorkspaceId: workspaceUser.workspaceId, activeWorkspace: workspaceUser.workspace.name, assignedWorkspaces, diff --git a/packages/server/src/enterprise/services/account.service.ts b/packages/server/src/enterprise/services/account.service.ts index 39e7591b8..e364370b6 100644 --- a/packages/server/src/enterprise/services/account.service.ts +++ b/packages/server/src/enterprise/services/account.service.ts @@ -510,7 +510,7 @@ export class AccountService { const personalWorkspaceRole = await this.roleService.readGeneralRoleByName(GeneralRole.PERSONAL_WORKSPACE, queryRunner) if (oldWorkspaceUser && oldWorkspaceUser.roleId !== personalWorkspaceRole.id) { - await this.workspaceUserService.deleteWorkspaceUser(oldWorkspaceUser.workspaceId, user.id) + await this.workspaceUserService.deleteWorkspaceUser(oldWorkspaceUser.workspaceId, user.id, data.workspace.organizationId) } await queryRunner.startTransaction() diff --git a/packages/server/src/enterprise/services/organization-user.service.ts b/packages/server/src/enterprise/services/organization-user.service.ts index 6e7eee717..19fd4f0d4 100644 --- a/packages/server/src/enterprise/services/organization-user.service.ts +++ b/packages/server/src/enterprise/services/organization-user.service.ts @@ -222,6 +222,8 @@ export class OrganizationUserService { throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, OrganizationUserErrorMessage.ORGANIZATION_USER_ALREADY_EXISTS) const role = await this.roleService.readRoleIsGeneral(data.roleId, queryRunner) if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) + if (role.name === GeneralRole.OWNER) + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, RoleErrorMessage.INVALID_ROLE_PERMISSIONS) const createdBy = await this.userService.readUserById(data.createdBy, queryRunner) if (!createdBy) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND) @@ -290,6 +292,8 @@ export class OrganizationUserService { if (newOrganizationUser.roleId) { const role = await this.roleService.readRoleIsGeneral(newOrganizationUser.roleId, queryRunner) if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) + if (role.name === GeneralRole.OWNER && organizationUser.roleId !== newOrganizationUser.roleId) + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, RoleErrorMessage.INVALID_ROLE_PERMISSIONS) } if (newOrganizationUser.status) this.validateOrganizationUserStatus(newOrganizationUser.status) diff --git a/packages/server/src/enterprise/services/workspace-user.service.ts b/packages/server/src/enterprise/services/workspace-user.service.ts index cd1172afa..f6a4c85fc 100644 --- a/packages/server/src/enterprise/services/workspace-user.service.ts +++ b/packages/server/src/enterprise/services/workspace-user.service.ts @@ -222,11 +222,13 @@ export class WorkspaceUserService { return await queryRunner.manager.save(WorkspaceUser, data) } - public async createWorkspaceUser(data: Partial) { + public async createWorkspaceUser(data: Partial, activeOrganizationId: string) { const queryRunner = this.dataSource.createQueryRunner() await queryRunner.connect() const { workspace, workspaceUser } = await this.readWorkspaceUserByWorkspaceIdUserId(data.workspaceId, data.userId, queryRunner) + if (workspace.organizationId !== activeOrganizationId) + throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) if (workspaceUser) throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, WorkspaceUserErrorMessage.WORKSPACE_USER_ALREADY_EXISTS) const role = await this.roleService.readRoleById(data.roleId, queryRunner) if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) @@ -330,13 +332,16 @@ export class WorkspaceUserService { return newWorkspace } - public async updateWorkspaceUser(newWorkspaserUser: Partial, queryRunner: QueryRunner) { + public async updateWorkspaceUser(newWorkspaserUser: Partial, queryRunner: QueryRunner, activeOrganizationId: string) { const { workspaceUser } = await this.readWorkspaceUserByWorkspaceIdUserId( newWorkspaserUser.workspaceId, newWorkspaserUser.userId, queryRunner ) if (!workspaceUser) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceUserErrorMessage.WORKSPACE_USER_NOT_FOUND) + const workspace = await this.workspaceService.readWorkspaceById(workspaceUser.workspaceId, queryRunner) + if (!workspace || workspace.organizationId !== activeOrganizationId) + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) if (newWorkspaserUser.roleId && workspaceUser.role) { const role = await this.roleService.readRoleById(newWorkspaserUser.roleId, queryRunner) if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) @@ -359,12 +364,18 @@ export class WorkspaceUserService { return updataWorkspaceUser } - public async deleteWorkspaceUser(workspaceId: string | undefined, userId: string | undefined) { + public async deleteWorkspaceUser( + workspaceId: string | undefined, + userId: string | undefined, + activeOrganizationId: string | undefined + ) { const queryRunner = this.dataSource.createQueryRunner() try { await queryRunner.connect() const { workspace, workspaceUser } = await this.readWorkspaceUserByWorkspaceIdUserId(workspaceId, userId, queryRunner) if (!workspaceUser) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceUserErrorMessage.WORKSPACE_USER_NOT_FOUND) + if (!workspace || workspace.organizationId !== activeOrganizationId) + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) const role = await this.roleService.readRoleById(workspaceUser.roleId, queryRunner) if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND) if (role.name === GeneralRole.OWNER) diff --git a/packages/server/src/enterprise/services/workspace.service.ts b/packages/server/src/enterprise/services/workspace.service.ts index fb8ec468e..4ac7300d9 100644 --- a/packages/server/src/enterprise/services/workspace.service.ts +++ b/packages/server/src/enterprise/services/workspace.service.ts @@ -137,7 +137,8 @@ export class WorkspaceService { await queryRunner.connect() const oldWorkspaceData = await this.readWorkspaceById(newWorkspaceData.id, queryRunner) - if (!oldWorkspaceData) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) + if (!oldWorkspaceData || oldWorkspaceData.organizationId !== newWorkspaceData.organizationId) + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) const user = await this.userService.readUserById(newWorkspaceData.updatedBy, queryRunner) if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND) if (newWorkspaceData.name) { @@ -161,9 +162,10 @@ export class WorkspaceService { return updateWorkspace } - public async deleteWorkspaceById(queryRunner: QueryRunner, workspaceId: string) { + public async deleteWorkspaceById(queryRunner: QueryRunner, workspaceId: string, activeOrganizationId: string) { const workspace = await this.readWorkspaceById(workspaceId, queryRunner) - if (!workspace) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) + if (!workspace || workspace.organizationId !== activeOrganizationId) + throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND) // First get all related entities that need to be deleted const chatflows = await queryRunner.manager.findBy(ChatFlow, { workspaceId }) diff --git a/packages/server/src/utils/constants.ts b/packages/server/src/utils/constants.ts index ae49d3c7c..01d22b5a8 100644 --- a/packages/server/src/utils/constants.ts +++ b/packages/server/src/utils/constants.ts @@ -57,7 +57,14 @@ export const WHITELIST_URLS = [ GithubSSO.CALLBACK_URI ] -export const API_KEY_BLACKLIST_URLS = ['/api/v1/nvidia-nim', '/api/v1/account/delete', '/api/v1/files'] +export const API_KEY_BLACKLIST_URLS = [ + '/api/v1/nvidia-nim', + '/api/v1/account/delete', + '/api/v1/files', + '/api/v1/organizationuser', + '/api/v1/workspace', + '/api/v1/workspaceuser' +] export const enum GeneralErrorMessage { FORBIDDEN = 'Forbidden',