From 81c26676a399f1d11a3964d6121a130de9f496d3 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Mon, 1 Sep 2025 16:15:53 +0200 Subject: [PATCH] fix: Expose projectId for remaining Data Table contexts (no-changelog) (#19058) --- .../base-dynamic-parameters-request.dto.ts | 1 + .../resource-locator-request.dto.ts | 1 - .../dynamic-node-parameters.controller.ts | 26 ++++++++++++++++++- .../src/components/ParameterInput.vue | 3 +++ .../src/components/ResourceMapper.test.ts | 8 +++++- .../ResourceMapper/ResourceMapper.vue | 3 +++ 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts index f0066fbf5a..6abebb5669 100644 --- a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts +++ b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts @@ -15,4 +15,5 @@ export class BaseDynamicParametersRequestDto extends Z.class({ credentials: z.record(z.string(), z.any()).optional() satisfies z.ZodType< INodeCredentials | undefined >, + projectId: z.string().optional(), }) {} diff --git a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts index ca2236566a..ac8e8df274 100644 --- a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts +++ b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts @@ -6,5 +6,4 @@ export class ResourceLocatorRequestDto extends BaseDynamicParametersRequestDto.e methodName: z.string(), filter: z.string().optional(), paginationToken: z.string().optional(), - projectId: z.string().optional(), }) {} diff --git a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts index 6dfaa512bc..386817da34 100644 --- a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts +++ b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts @@ -29,10 +29,22 @@ export class DynamicNodeParametersController { path, methodName, loadOptions, + projectId, } = payload; const additionalData = await getBase(req.user.id, currentNodeParameters); + if (projectId) { + if (await userHasScopes(req.user, ['dataStore:listProject'], false, { projectId })) { + // Project ID is currently only added on the additionalData if the user + // has data store listing permission for that project. We should consider + // turning this into a more general check, but as of now data stores are + // the only nodes with project specific resource locators where we want to ensure + // that only data stores belonging to their respective projects are shown. + additionalData.dataStoreProjectId = projectId; + } + } + if (methodName) { return await this.service.getOptionsViaMethodName( methodName, @@ -105,10 +117,22 @@ export class DynamicNodeParametersController { _res: Response, @Body payload: ResourceMapperFieldsRequestDto, ) { - const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion } = payload; + const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion, projectId } = + payload; const additionalData = await getBase(req.user.id, currentNodeParameters); + if (projectId) { + if (await userHasScopes(req.user, ['dataStore:listProject'], false, { projectId })) { + // Project ID is currently only added on the additionalData if the user + // has data store listing permission for that project. We should consider + // turning this into a more general check, but as of now data stores are + // the only nodes with project specific resource locators where we want to ensure + // that only data stores belonging to their respective projects are shown. + additionalData.dataStoreProjectId = projectId; + } + } + return await this.service.getResourceMappingFields( methodName, path, diff --git a/packages/frontend/editor-ui/src/components/ParameterInput.vue b/packages/frontend/editor-ui/src/components/ParameterInput.vue index ca02141847..4beb074fa9 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInput.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInput.vue @@ -87,6 +87,7 @@ import CssEditor from './CssEditor/CssEditor.vue'; import { useFocusPanelStore } from '@/stores/focusPanel.store'; import ExperimentalEmbeddedNdvMapper from '@/components/canvas/experimental/components/ExperimentalEmbeddedNdvMapper.vue'; import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store'; +import { useProjectsStore } from '@/stores/projects.store'; type Picker = { $emit: (arg0: string, arg1: Date) => void }; @@ -152,6 +153,7 @@ const nodeTypesStore = useNodeTypesStore(); const uiStore = useUIStore(); const focusPanelStore = useFocusPanelStore(); const experimentalNdvStore = useExperimentalNdvStore(); +const projectsStore = useProjectsStore(); const expressionLocalResolveCtx = inject(ExpressionLocalResolveContextSymbol, undefined); @@ -688,6 +690,7 @@ async function loadRemoteParameterOptions() { loadOptions, currentNodeParameters: resolvedNodeParameters, credentials: node.value.credentials, + projectId: projectsStore.currentProjectId, }); remoteParameterOptions.value = remoteParameterOptions.value.concat(options); diff --git a/packages/frontend/editor-ui/src/components/ResourceMapper.test.ts b/packages/frontend/editor-ui/src/components/ResourceMapper.test.ts index fdc06099cf..f8beff377e 100644 --- a/packages/frontend/editor-ui/src/components/ResourceMapper.test.ts +++ b/packages/frontend/editor-ui/src/components/ResourceMapper.test.ts @@ -4,13 +4,17 @@ import { UPDATED_SCHEMA, } from './__tests__/utils/ResourceMapper.utils'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; -import { cleanupAppModals, createAppModals, waitAllPromises } from '@/__tests__/utils'; +import type { MockedStore } from '@/__tests__/utils'; +import { cleanupAppModals, createAppModals, mockedStore, waitAllPromises } from '@/__tests__/utils'; import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue'; import userEvent from '@testing-library/user-event'; import { createComponentRenderer } from '@/__tests__/render'; import type { MockInstance } from 'vitest'; +import { useProjectsStore } from '@/stores/projects.store'; let nodeTypeStore: ReturnType; +let projectsStore: MockedStore; + let fetchFieldsSpy: MockInstance; const renderComponent = createComponentRenderer(ResourceMapper, DEFAULT_SETUP); @@ -25,6 +29,8 @@ describe('ResourceMapper.vue', () => { beforeEach(() => { createAppModals(); + projectsStore = mockedStore(useProjectsStore); + projectsStore.currentProjectId = 'aProjectId'; }); afterEach(() => { diff --git a/packages/frontend/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/frontend/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index 0d3f1c434b..75187add55 100644 --- a/packages/frontend/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/frontend/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -30,6 +30,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { useDocumentVisibility } from '@/composables/useDocumentVisibility'; import { N8nButton, N8nCallout, N8nNotice } from '@n8n/design-system'; import isEqual from 'lodash/isEqual'; +import { useProjectsStore } from '@/stores/projects.store'; type Props = { parameter: INodeProperties; @@ -46,6 +47,7 @@ type Props = { const nodeTypesStore = useNodeTypesStore(); const ndvStore = useNDVStore(); const workflowsStore = useWorkflowsStore(); +const projectsStore = useProjectsStore(); const props = withDefaults(defineProps(), { teleported: true, @@ -310,6 +312,7 @@ const createRequestParams = (methodName: string) => { path: props.path, methodName, credentials: props.node.credentials, + projectId: projectsStore.currentProjectId, }; return requestParams;