diff --git a/packages/frontend/@n8n/stores/package.json b/packages/frontend/@n8n/stores/package.json index 243e10bdbcf..b7d0d212023 100644 --- a/packages/frontend/@n8n/stores/package.json +++ b/packages/frontend/@n8n/stores/package.json @@ -36,6 +36,8 @@ "format:check": "biome ci . && prettier --check . --ignore-path ../../../../.prettierignore" }, "dependencies": { + "@n8n/api-types": "workspace:*", + "@n8n/permissions": "workspace:*", "n8n-workflow": "workspace:*", "@vueuse/core": "catalog:frontend", "pinia": "catalog:frontend", diff --git a/packages/frontend/editor-ui/src/app/stores/rbac.store.test.ts b/packages/frontend/@n8n/stores/src/rbac.store.test.ts similarity index 95% rename from packages/frontend/editor-ui/src/app/stores/rbac.store.test.ts rename to packages/frontend/@n8n/stores/src/rbac.store.test.ts index a05e2f10b53..0b038b09d18 100644 --- a/packages/frontend/editor-ui/src/app/stores/rbac.store.test.ts +++ b/packages/frontend/@n8n/stores/src/rbac.store.test.ts @@ -1,10 +1,12 @@ -import { createPinia, setActivePinia } from 'pinia'; -import { useRBACStore } from '@/app/stores/rbac.store'; +import type * as PermissionsModule from '@n8n/permissions'; import type { Scope } from '@n8n/permissions'; import { hasScope } from '@n8n/permissions'; +import { createPinia, setActivePinia } from 'pinia'; + +import { useRBACStore } from './rbac.store'; vi.mock('@n8n/permissions', async () => { - const { hasScope } = await vi.importActual('@n8n/permissions'); + const { hasScope } = await vi.importActual('@n8n/permissions'); return { hasScope: vi.fn().mockImplementation(hasScope), }; diff --git a/packages/frontend/@n8n/stores/src/rbac.store.ts b/packages/frontend/@n8n/stores/src/rbac.store.ts new file mode 100644 index 00000000000..8f716102b8f --- /dev/null +++ b/packages/frontend/@n8n/stores/src/rbac.store.ts @@ -0,0 +1,154 @@ +import type { Role } from '@n8n/api-types'; +import { hasScope as genericHasScope } from '@n8n/permissions'; +import type { ScopeOptions, Scope, Resource } from '@n8n/permissions'; +import { defineStore } from 'pinia'; +import { ref } from 'vue'; + +import { STORES } from './constants'; + +export const useRBACStore = defineStore(STORES.RBAC, () => { + const globalRoles = ref([]); + const rolesByProjectId = ref>({}); + + const globalScopes = ref([]); + const scopesByProjectId = ref>({}); + const scopesByResourceId = ref>>({ + agent: {}, + aiAssistant: {}, + workflow: {}, + tag: {}, + annotationTag: {}, + user: {}, + credential: {}, + variable: {}, + projectVariable: {}, + sourceControl: {}, + externalSecretsProvider: {}, + externalSecret: {}, + project: {}, + orchestration: {}, + workersView: {}, + eventBusDestination: {}, + auditLogs: {}, + banner: {}, + community: {}, + communityPackage: {}, + ldap: {}, + license: {}, + logStreaming: {}, + saml: {}, + oidc: {}, + provisioning: {}, + securityAudit: {}, + folder: {}, + insights: {}, + dataTable: {}, + execution: {}, + testRun: {}, + workflowTags: {}, + role: {}, + mcp: {}, + mcpApiKey: {}, + chatHub: {}, + chatHubAgent: {}, + breakingChanges: {}, + apiKey: {}, + encryptionKey: {}, + credentialResolver: {}, + instanceAi: {}, + securitySettings: {}, + roleMappingRule: {}, + otel: {}, + }); + + function addGlobalRole(role: Role) { + if (!globalRoles.value.includes(role)) { + globalRoles.value.push(role); + } + } + + function hasRole(role: Role) { + return globalRoles.value.includes(role); + } + + function addGlobalScope(scope: Scope) { + if (!globalScopes.value.includes(scope)) { + globalScopes.value.push(scope); + } + } + + function setGlobalScopes(scopes: Scope[]) { + globalScopes.value = scopes; + } + + function addProjectScope( + scope: Scope, + context: { + projectId: string; + }, + ) { + if (!scopesByProjectId.value[context.projectId]) { + scopesByProjectId.value[context.projectId] = []; + } + + if (!scopesByProjectId.value[context.projectId].includes(scope)) { + scopesByProjectId.value[context.projectId].push(scope); + } + } + + function addResourceScope( + scope: Scope, + context: { + resourceType: Resource; + resourceId: string; + }, + ) { + const scopesByResourceType = scopesByResourceId.value[context.resourceType]; + if (!scopesByResourceType[context.resourceId]) { + scopesByResourceType[context.resourceId] = []; + } + + if (!scopesByResourceType[context.resourceId].includes(scope)) { + scopesByResourceType[context.resourceId].push(scope); + } + } + + function hasScope( + scope: Scope | Scope[], + context?: { + resourceType?: Resource; + resourceId?: string; + projectId?: string; + }, + options?: ScopeOptions, + ): boolean { + return genericHasScope( + scope, + { + global: globalScopes.value, + project: context?.projectId ? scopesByProjectId.value[context.projectId] : [], + resource: + context?.resourceType && context?.resourceId + ? scopesByResourceId.value[context.resourceType][context.resourceId] + : [], + }, + undefined, + options, + ); + } + + return { + globalRoles, + rolesByProjectId, + globalScopes, + scopesByProjectId, + scopesByResourceId, + addGlobalRole, + hasRole, + addGlobalScope, + setGlobalScopes, + addProjectScope, + addResourceScope, + hasScope, + }; +}); diff --git a/packages/frontend/editor-ui/src/app/stores/rbac.store.ts b/packages/frontend/editor-ui/src/app/stores/rbac.store.ts index 65bc2b58db1..35af136fb25 100644 --- a/packages/frontend/editor-ui/src/app/stores/rbac.store.ts +++ b/packages/frontend/editor-ui/src/app/stores/rbac.store.ts @@ -1,153 +1,6 @@ -import { defineStore } from 'pinia'; -import { hasScope as genericHasScope } from '@n8n/permissions'; -import type { ScopeOptions, Scope, Resource } from '@n8n/permissions'; -import { ref } from 'vue'; -import { STORES } from '@n8n/stores'; -import type { Role } from '@n8n/api-types'; - -export const useRBACStore = defineStore(STORES.RBAC, () => { - const globalRoles = ref([]); - const rolesByProjectId = ref>({}); - - const globalScopes = ref([]); - const scopesByProjectId = ref>({}); - const scopesByResourceId = ref>>({ - agent: {}, - aiAssistant: {}, - workflow: {}, - tag: {}, - annotationTag: {}, - user: {}, - credential: {}, - variable: {}, - projectVariable: {}, - sourceControl: {}, - externalSecretsProvider: {}, - externalSecret: {}, - project: {}, - orchestration: {}, - workersView: {}, - eventBusDestination: {}, - auditLogs: {}, - banner: {}, - community: {}, - communityPackage: {}, - ldap: {}, - license: {}, - logStreaming: {}, - saml: {}, - oidc: {}, - provisioning: {}, - securityAudit: {}, - folder: {}, - insights: {}, - dataTable: {}, - execution: {}, - testRun: {}, - workflowTags: {}, - role: {}, - mcp: {}, - mcpApiKey: {}, - chatHub: {}, - chatHubAgent: {}, - breakingChanges: {}, - apiKey: {}, - encryptionKey: {}, - credentialResolver: {}, - instanceAi: {}, - securitySettings: {}, - roleMappingRule: {}, - otel: {}, - }); - - function addGlobalRole(role: Role) { - if (!globalRoles.value.includes(role)) { - globalRoles.value.push(role); - } - } - - function hasRole(role: Role) { - return globalRoles.value.includes(role); - } - - function addGlobalScope(scope: Scope) { - if (!globalScopes.value.includes(scope)) { - globalScopes.value.push(scope); - } - } - - function setGlobalScopes(scopes: Scope[]) { - globalScopes.value = scopes; - } - - function addProjectScope( - scope: Scope, - context: { - projectId: string; - }, - ) { - if (!scopesByProjectId.value[context.projectId]) { - scopesByProjectId.value[context.projectId] = []; - } - - if (!scopesByProjectId.value[context.projectId].includes(scope)) { - scopesByProjectId.value[context.projectId].push(scope); - } - } - - function addResourceScope( - scope: Scope, - context: { - resourceType: Resource; - resourceId: string; - }, - ) { - const scopesByResourceType = scopesByResourceId.value[context.resourceType]; - if (!scopesByResourceType[context.resourceId]) { - scopesByResourceType[context.resourceId] = []; - } - - if (!scopesByResourceType[context.resourceId].includes(scope)) { - scopesByResourceType[context.resourceId].push(scope); - } - } - - function hasScope( - scope: Scope | Scope[], - context?: { - resourceType?: Resource; - resourceId?: string; - projectId?: string; - }, - options?: ScopeOptions, - ): boolean { - return genericHasScope( - scope, - { - global: globalScopes.value, - project: context?.projectId ? scopesByProjectId.value[context.projectId] : [], - resource: - context?.resourceType && context?.resourceId - ? scopesByResourceId.value[context.resourceType][context.resourceId] - : [], - }, - undefined, - options, - ); - } - - return { - globalRoles, - rolesByProjectId, - globalScopes, - scopesByProjectId, - scopesByResourceId, - addGlobalRole, - hasRole, - addGlobalScope, - setGlobalScopes, - addProjectScope, - addResourceScope, - hasScope, - }; -}); +/** + * @deprecated Import from `@n8n/stores/rbac.store` instead. This store moved to + * `@n8n/stores` (CAT-3686 kernel slice); this re-export is a temporary shim kept + * so existing importers keep working and will be removed once call sites migrate. + */ +export * from '@n8n/stores/rbac.store'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97d900a8c5d..14f61c4d85e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5207,6 +5207,12 @@ importers: packages/frontend/@n8n/stores: dependencies: + '@n8n/api-types': + specifier: workspace:* + version: link:../../../@n8n/api-types + '@n8n/permissions': + specifier: workspace:* + version: link:../../../@n8n/permissions '@vueuse/core': specifier: catalog:frontend version: 10.11.0(vue@3.5.26(typescript@6.0.2))