chore(core): Use dynamic role resolution for access control (#19400)

This commit is contained in:
Andreas Fitzek
2025-09-17 11:15:31 +02:00
committed by GitHub
parent 8086a21eb2
commit 33a2d5de17
21 changed files with 1581 additions and 201 deletions
@@ -1,17 +1,20 @@
import type { SharedWorkflow, User } from '@n8n/db';
import { SharedWorkflowRepository, FolderRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import { hasGlobalScope, rolesWithScope, type Scope } from '@n8n/permissions';
import { hasGlobalScope, type Scope } from '@n8n/permissions';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import { In } from '@n8n/typeorm';
import { RoleService } from '@/services/role.service';
@Service()
export class WorkflowFinderService {
constructor(
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
private readonly folderRepository: FolderRepository,
private readonly roleService: RoleService,
) {}
async findWorkflowForUser(
@@ -27,8 +30,10 @@ export class WorkflowFinderService {
let where: FindOptionsWhere<SharedWorkflow> = {};
if (!hasGlobalScope(user, scopes, { mode: 'allOf' })) {
const projectRoles = rolesWithScope('project', scopes);
const workflowRoles = rolesWithScope('workflow', scopes);
const [projectRoles, workflowRoles] = await Promise.all([
this.roleService.rolesWithScope('project', scopes),
this.roleService.rolesWithScope('workflow', scopes),
]);
where = {
role: In(workflowRoles),
@@ -78,8 +83,10 @@ export class WorkflowFinderService {
}
if (!hasGlobalScope(user, scopes, { mode: 'allOf' })) {
const projectRoles = rolesWithScope('project', scopes);
const workflowRoles = rolesWithScope('workflow', scopes);
const [projectRoles, workflowRoles] = await Promise.all([
this.roleService.rolesWithScope('project', scopes),
this.roleService.rolesWithScope('workflow', scopes),
]);
where = {
...where,
@@ -3,7 +3,6 @@ import { ProjectRelationRepository, SharedWorkflowRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import {
hasGlobalScope,
rolesWithScope,
type ProjectRole,
type WorkflowSharingRole,
type Scope,
@@ -47,9 +46,13 @@ export class WorkflowSharingService {
}
const projectRoles =
'scopes' in options ? rolesWithScope('project', options.scopes) : options.projectRoles;
'scopes' in options
? await this.roleService.rolesWithScope('project', options.scopes)
: options.projectRoles;
const workflowRoles =
'scopes' in options ? rolesWithScope('workflow', options.scopes) : options.workflowRoles;
'scopes' in options
? await this.roleService.rolesWithScope('workflow', options.scopes)
: options.workflowRoles;
const sharedWorkflows = await this.sharedWorkflowRepository.find({
where: {
@@ -140,8 +140,6 @@ export class WorkflowsController {
let project: Project | null;
const savedWorkflow = await dbManager.transaction(async (transactionManager) => {
const workflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
const { projectId, parentFolderId } = req.body;
project =
projectId === undefined
@@ -164,6 +162,8 @@ export class WorkflowsController {
throw new UnexpectedError('No personal project found');
}
const workflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
if (parentFolderId) {
try {
const parentFolder = await this.folderService.findFolderInProjectOrFail(