diff --git a/packages/server/src/enterprise/controllers/workspace-user.controller.ts b/packages/server/src/enterprise/controllers/workspace-user.controller.ts index 90ba4f2e2e5..b2c9945f7eb 100644 --- a/packages/server/src/enterprise/controllers/workspace-user.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace-user.controller.ts @@ -7,6 +7,7 @@ import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { WorkspaceUser } from '../database/entities/workspace-user.entity' import { WorkspaceUserService } from '../services/workspace-user.service' import { + assertMayReadTargetUser, assertQueryOrganizationMatchesActiveOrg, assertWorkspaceIdAccessibleToUser, getLoggedInUser, @@ -35,6 +36,7 @@ export class WorkspaceUserController { let workspaceUser: any if (query.workspaceId && query.userId) { + // Caller must have access to the workspace (own, assigned, or org admin within their org). await assertWorkspaceIdAccessibleToUser(user, query.workspaceId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceIdUserId( query.workspaceId, @@ -42,28 +44,35 @@ export class WorkspaceUserController { queryRunner ) } else if (query.workspaceId) { + // Caller must have access to the workspace (own, assigned, or org admin within their org). await assertWorkspaceIdAccessibleToUser(user, query.workspaceId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceId(query.workspaceId, queryRunner) } else if (query.organizationId && query.userId) { + // organizationId must match the caller's active org to prevent cross-org access. + // Caller must be the target user or an org user manager whose target belongs to the same org (IDOR guard). assertQueryOrganizationMatchesActiveOrg(user, query.organizationId) - if (query.userId !== user.id && !userMayManageOrgUsers(user)) { - throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) - } + await assertMayReadTargetUser(user, query.userId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( query.organizationId, query.userId, queryRunner ) } else if (query.userId) { - if (query.userId !== user.id && !userMayManageOrgUsers(user)) { - throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) + if (query.userId === user.id) { + // Self-lookup: return memberships across all orgs so the user can switch to an invited org/workspace. + workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(query.userId, queryRunner) + } else { + // Non-self: caller must be an org user manager and the target must belong to the caller's active org (IDOR guard). + // Results are scoped to the caller's active org to prevent cross-org data leakage. + await assertMayReadTargetUser(user, query.userId, queryRunner) + workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( + user.activeOrganizationId, + query.userId, + queryRunner + ) } - workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( - user.activeOrganizationId, - query.userId, - queryRunner - ) } else if (query.roleId) { + // Only org user managers may list workspace members by role. if (!userMayManageOrgUsers(user)) { throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) }