Fix Flowise 788 Improve Tenant Validation (#6620)

* fix(flowise-788): improve tenant validation

* fix: use updatedBy instead of createdBy in organization user update
This commit is contained in:
yau-wd
2026-07-15 11:54:38 +08:00
committed by GitHub
parent bb773ffa71
commit 9fcb7ffbd5
9 changed files with 89 additions and 17 deletions
@@ -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<OrganizationUser>
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
@@ -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<WorkspaceUser>
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)
@@ -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)
@@ -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,
@@ -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()
@@ -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)
@@ -222,11 +222,13 @@ export class WorkspaceUserService {
return await queryRunner.manager.save(WorkspaceUser, data)
}
public async createWorkspaceUser(data: Partial<WorkspaceUser>) {
public async createWorkspaceUser(data: Partial<WorkspaceUser>, 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<WorkspaceUser>, queryRunner: QueryRunner) {
public async updateWorkspaceUser(newWorkspaserUser: Partial<WorkspaceUser>, 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)
@@ -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 })
+8 -1
View File
@@ -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',