refactor(core): Move role services to backend services (no-changelog) (#39999)

This commit is contained in:
Charlie Kolb 2026-10-01 09:13:58 +00:00 committed by GitHub
parent 8dc0ac8011
commit 577df7a23b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 316 additions and 253 deletions

4
OWNERS
View File

@ -28,6 +28,8 @@ packages/@n8n/config/ @n8n-io/catalysts
packages/@n8n/backend-common/ @n8n-io/catalysts
packages/@n8n/backend-network/ @n8n-io/catalysts
packages/@n8n/backend-services/ @n8n-io/catalysts
packages/@n8n/backend-services/src/services/role.service.ts @n8n-io/iam
packages/@n8n/backend-services/src/services/role-cache.service.ts @n8n-io/iam
packages/@n8n/backend-test-utils/ @n8n-io/catalysts
packages/@n8n/di/ @n8n-io/catalysts
packages/@n8n/errors/ @n8n-io/catalysts
@ -155,8 +157,6 @@ packages/cli/src/controllers/e2e.controller.ts @n8n-io/qa-dx
# CLI services
packages/cli/src/services/jwt.service.ts @n8n-io/iam
packages/cli/src/services/user.service.ts @n8n-io/iam
packages/cli/src/services/role.service.ts @n8n-io/iam
packages/cli/src/services/role-cache.service.ts @n8n-io/iam
packages/cli/src/services/password.utility.ts @n8n-io/iam
packages/cli/src/services/public-api-key.service.ts @n8n-io/iam
packages/cli/src/services/security-settings.service.ts @n8n-io/iam

View File

@ -28,8 +28,10 @@
"@n8n/config": "workspace:*",
"@n8n/constants": "workspace:*",
"@n8n/decorators": "workspace:*",
"@n8n/db": "workspace:*",
"@n8n/di": "workspace:*",
"@n8n/errors": "workspace:*",
"@n8n/permissions": "workspace:*",
"@n8n/utils": "workspace:*",
"cache-manager": "catalog:",
"express-openapi-validator": "5.5.3",

View File

@ -3,6 +3,12 @@ export { UncacheableValueError } from './errors/cache-errors/uncacheable-value.e
export { CacheService } from './services/cache/cache.service';
export { RedisClientService } from './services/redis-client.service';
export type { RedisClientType } from './services/redis.types';
export { RoleCacheService, type RoleLoader } from './services/role-cache.service';
export {
RoleDeletionCheckProxy,
type RoleDeletionChecker,
} from './services/role-deletion-check-proxy.service';
export { RoleService } from './services/role.service';
export { UrlService } from './services/url.service';
export {
classifyRestError,

View File

@ -1,13 +1,11 @@
import { Logger } from '@n8n/backend-common';
import { mockInstance } from '@n8n/backend-test-utils';
import { RoleRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import { staticRolesWithScope } from '@n8n/permissions';
import type { MockedFunction } from 'vitest';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@n8n/backend-services';
import { RoleCacheService } from '@/services/role-cache.service';
import { type CacheService } from '../cache/cache.service';
import { RoleCacheService } from '../role-cache.service';
// Mock static function
vi.mock('@n8n/permissions', async () => ({
@ -17,11 +15,11 @@ vi.mock('@n8n/permissions', async () => ({
describe('RoleCacheService', () => {
const cacheService = mock<CacheService>();
const logger = mockInstance(Logger);
const roleRepository = mockInstance(RoleRepository);
const logger = mock<Logger>();
const roleRepository = mock<RoleRepository>();
const staticRolesMock = staticRolesWithScope as MockedFunction<typeof staticRolesWithScope>;
const roleCacheService = new RoleCacheService(cacheService, logger);
const roleCacheService = new RoleCacheService(cacheService, logger, roleRepository);
const mockRoleScopeMap = {
project: {
@ -49,7 +47,6 @@ describe('RoleCacheService', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(Container, 'get').mockReturnValue(roleRepository);
});
describe('getRolesWithAllScopes', () => {
@ -186,7 +183,6 @@ describe('RoleCacheService', () => {
await roleCacheService.refreshCache();
expect(Container.get).toHaveBeenCalledWith(RoleRepository);
expect(roleRepository.findAll).toHaveBeenCalledTimes(1);
expect(cacheService.set).toHaveBeenCalledWith(
'roles:scope-map',

View File

@ -3,7 +3,7 @@ import { mock } from 'vitest-mock-extended';
import {
RoleDeletionCheckProxy,
type RoleDeletionChecker,
} from '@/services/role-deletion-check-proxy.service';
} from '../role-deletion-check-proxy.service';
describe('RoleDeletionCheckProxy', () => {
it('reports no blockers when no provider is registered', async () => {

View File

@ -1,23 +1,21 @@
import type { LicenseState } from '@n8n/backend-common';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import { EventService } from '../../events/event.service';
import { RoleCacheService } from '../role-cache.service';
import { RoleDeletionCheckProxy } from '../role-deletion-check-proxy.service';
import { RoleService } from '../role.service';
import type { CredentialsEntity, ScopesField, User } from '@n8n/db';
import { RoleRepository, ScopeRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleDeletionCheckProxy } from '@/services/role-deletion-check-proxy.service';
import { RoleService } from '@/services/role.service';
describe('RoleService.addScopes', () => {
const licenseState = mock<LicenseState>();
const roleRepository = mockInstance(RoleRepository);
const scopeRepository = mockInstance(ScopeRepository);
const roleCacheService = mockInstance(RoleCacheService);
const logger = mockInstance(Logger);
const roleDeletionCheckProxy = mockInstance(RoleDeletionCheckProxy);
const eventService = mockInstance(EventService);
const roleRepository = mock<RoleRepository>();
const scopeRepository = mock<ScopeRepository>();
const roleCacheService = mock<RoleCacheService>();
const logger = mock<Logger>();
const roleDeletionCheckProxy = mock<RoleDeletionCheckProxy>();
const eventService = mock<EventService>();
const roleService = new RoleService(
licenseState,

View File

@ -1,23 +1,22 @@
import type { LicenseState } from '@n8n/backend-common';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import { EventService } from '../../events/event.service';
import { RoleCacheService } from '../role-cache.service';
import { RoleDeletionCheckProxy } from '../role-deletion-check-proxy.service';
import { RoleService } from '../role.service';
import { RoleRepository, ScopeRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import { NotFoundError } from '@n8n/errors';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleDeletionCheckProxy } from '@/services/role-deletion-check-proxy.service';
import { RoleService } from '@/services/role.service';
describe('RoleService.getRoleAssignments and getRoleProjectMembers', () => {
const licenseState = mock<LicenseState>();
const roleRepository = mockInstance(RoleRepository);
const scopeRepository = mockInstance(ScopeRepository);
const roleCacheService = mockInstance(RoleCacheService);
const logger = mockInstance(Logger);
const roleDeletionCheckProxy = mockInstance(RoleDeletionCheckProxy);
const eventService = mockInstance(EventService);
const roleRepository = mock<RoleRepository>();
const scopeRepository = mock<ScopeRepository>();
const roleCacheService = mock<RoleCacheService>();
const logger = mock<Logger>();
const roleDeletionCheckProxy = mock<RoleDeletionCheckProxy>();
const eventService = mock<EventService>();
const roleService = new RoleService(
licenseState,

View File

@ -1,26 +1,25 @@
import type { CreateRoleDto, UpdateRoleDto } from '@n8n/api-types';
import type { LicenseState } from '@n8n/backend-common';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import { EventService } from '../../events/event.service';
import { RoleCacheService } from '../role-cache.service';
import { RoleDeletionCheckProxy } from '../role-deletion-check-proxy.service';
import { RoleService } from '../role.service';
import type { Role, Scope as DBScope } from '@n8n/db';
import { RoleRepository, ScopeRepository } from '@n8n/db';
import { MANDATORY_INSTANCE_SCOPES } from '@n8n/permissions';
import { mock } from 'vitest-mock-extended';
import { BadRequestError } from '@n8n/errors';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleDeletionCheckProxy } from '@/services/role-deletion-check-proxy.service';
import { RoleService } from '@/services/role.service';
describe('RoleService custom role scope whitelist', () => {
const licenseState = mock<LicenseState>();
const roleRepository = mockInstance(RoleRepository);
const scopeRepository = mockInstance(ScopeRepository);
const roleCacheService = mockInstance(RoleCacheService);
const logger = mockInstance(Logger);
const roleDeletionCheckProxy = mockInstance(RoleDeletionCheckProxy);
const eventService = mockInstance(EventService);
const roleRepository = mock<RoleRepository>();
const scopeRepository = mock<ScopeRepository>();
const roleCacheService = mock<RoleCacheService>();
const logger = mock<Logger>();
const roleDeletionCheckProxy = mock<RoleDeletionCheckProxy>();
const eventService = mock<EventService>();
const roleService = new RoleService(
licenseState,

View File

@ -1,22 +1,20 @@
import type { LicenseState } from '@n8n/backend-common';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import { EventService } from '../../events/event.service';
import { RoleCacheService } from '../role-cache.service';
import { RoleDeletionCheckProxy } from '../role-deletion-check-proxy.service';
import { RoleService } from '../role.service';
import { RoleRepository, ScopeRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleDeletionCheckProxy } from '@/services/role-deletion-check-proxy.service';
import { RoleService } from '@/services/role.service';
describe('RoleService.rolesWithScope', () => {
const licenseState = mock<LicenseState>();
const roleRepository = mockInstance(RoleRepository);
const scopeRepository = mockInstance(ScopeRepository);
const roleCacheService = mockInstance(RoleCacheService);
const logger = mockInstance(Logger);
const roleDeletionCheckProxy = mockInstance(RoleDeletionCheckProxy);
const eventService = mockInstance(EventService);
const roleRepository = mock<RoleRepository>();
const scopeRepository = mock<ScopeRepository>();
const roleCacheService = mock<RoleCacheService>();
const logger = mock<Logger>();
const roleDeletionCheckProxy = mock<RoleDeletionCheckProxy>();
const eventService = mock<EventService>();
const roleService = new RoleService(
licenseState,
@ -39,11 +37,9 @@ describe('RoleService.rolesWithScope', () => {
const result = await roleService.rolesWithScope('project', 'project:read');
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith(
'project',
['project:read'],
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith('project', [
'project:read',
]);
expect(result).toEqual(mockRoles);
});
@ -54,11 +50,7 @@ describe('RoleService.rolesWithScope', () => {
const result = await roleService.rolesWithScope('project', inputScopes);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith(
'project',
inputScopes,
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith('project', inputScopes);
expect(result).toEqual(mockRoles);
});
@ -68,7 +60,7 @@ describe('RoleService.rolesWithScope', () => {
const result = await roleService.rolesWithScope('project', []);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith('project', [], undefined);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith('project', []);
expect(result).toEqual([]);
});
});
@ -110,11 +102,10 @@ describe('RoleService.rolesWithScope', () => {
'project:update',
]);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith(
'project',
['project:read', 'project:update'],
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith('project', [
'project:read',
'project:update',
]);
expect(result).toEqual(mockRoles);
});
});
@ -130,24 +121,15 @@ describe('RoleService.rolesWithScope', () => {
await roleService.rolesWithScope('project', 'project:delete');
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledTimes(3);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenNthCalledWith(
1,
'credential',
['credential:read'],
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenNthCalledWith(
2,
'workflow',
['workflow:execute'],
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenNthCalledWith(
3,
'project',
['project:delete'],
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenNthCalledWith(1, 'credential', [
'credential:read',
]);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenNthCalledWith(2, 'workflow', [
'workflow:execute',
]);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenNthCalledWith(3, 'project', [
'project:delete',
]);
});
it('should handle mixed scope arrays', async () => {
@ -161,11 +143,7 @@ describe('RoleService.rolesWithScope', () => {
const result = await roleService.rolesWithScope('project', mixedScopes);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith(
'project',
mixedScopes,
undefined,
);
expect(roleCacheService.getRolesWithAllScopes).toHaveBeenCalledWith('project', mixedScopes);
expect(result).toEqual(mockRoles);
});
});

View File

@ -1,11 +1,10 @@
import { Logger } from '@n8n/backend-common';
import { Time } from '@n8n/constants';
import { RoleRepository } from '@n8n/db';
import type { EntityManager } from '@n8n/db';
import { Container, Service } from '@n8n/di';
import { Service } from '@n8n/di';
import { staticRolesWithScope, type Scope } from '@n8n/permissions';
import { CacheService } from '@n8n/backend-services';
import { CacheService } from './cache/cache.service';
type RoleInfo = {
scopes: string[]; // array of scope slugs
@ -29,6 +28,8 @@ interface RoleScopeMap {
};
}
export type RoleLoader = () => ReturnType<RoleRepository['findAll']>;
@Service()
export class RoleCacheService {
private static readonly CACHE_KEY = 'roles:scope-map';
@ -37,15 +38,15 @@ export class RoleCacheService {
constructor(
private readonly cacheService: CacheService,
private readonly logger: Logger,
private readonly roleRepository: RoleRepository,
) {}
/**
* Get all roles from database and build scope map
*/
private async buildRoleScopeMap(trx?: EntityManager): Promise<RoleScopeMap> {
private async buildRoleScopeMap(loadRoles?: RoleLoader): Promise<RoleScopeMap> {
try {
const roleRepository = Container.get(RoleRepository);
const roles = await roleRepository.findAll(trx);
const roles = await (loadRoles?.() ?? this.roleRepository.findAll());
const roleScopeMap: RoleScopeMap = {};
for (const role of roles) {
@ -68,13 +69,13 @@ export class RoleCacheService {
async getRolesWithAllScopes(
namespace: 'global' | 'project' | 'credential' | 'workflow' | 'secretsProviderConnection',
requiredScopes: Scope[],
em?: EntityManager,
loadRoles?: RoleLoader,
): Promise<string[]> {
if (requiredScopes.length === 0) return [];
// Get cached role map with refresh function
const roleScopeMap = await this.cacheService.get<RoleScopeMap>(RoleCacheService.CACHE_KEY, {
refreshFn: async () => await this.buildRoleScopeMap(em),
refreshFn: async () => await this.buildRoleScopeMap(loadRoles),
fallbackValue: undefined,
});

View File

@ -5,7 +5,6 @@ import type {
} from '@n8n/api-types';
import { CreateRoleDto } from '@n8n/api-types';
import { LicenseState, Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import {
CredentialsEntity,
SharedCredentials,
@ -19,16 +18,10 @@ import {
Scope as DBScope,
ScopeRepository,
GLOBAL_ADMIN_ROLE,
isUniqueConstraintError,
} from '@n8n/db';
import type { EntityManager } from '@n8n/db';
import { Service } from '@n8n/di';
import type {
Scope,
Role as RoleDTO,
AssignableProjectRole,
AssignableGlobalRole,
RoleNamespace,
} from '@n8n/permissions';
import type { Scope, Role as RoleDTO, RoleNamespace } from '@n8n/permissions';
import {
combineScopes,
CUSTOM_ROLE_SCOPE_WHITELIST,
@ -43,10 +36,16 @@ import {
import { UnexpectedError, UserError } from 'n8n-workflow';
import { BadRequestError, NotFoundError } from '@n8n/errors';
import { isUniqueConstraintError } from '@/response-helper';
import { RoleCacheService } from './role-cache.service';
import { RoleCacheService, type RoleLoader } from './role-cache.service';
import { RoleDeletionCheckProxy } from './role-deletion-check-proxy.service';
import { EventService } from '../events/event.service';
declare module '../events/event.service' {
interface EventMap {
'custom-role-deleted': { userId: string; roleSlug: string };
'custom-role-updated': { userId: string; roleSlug: string; scopes: string[] };
}
}
@Service()
export class RoleService {
@ -458,16 +457,18 @@ export class RoleService {
async rolesWithScope(
namespace: RoleNamespace,
scopes: Scope | Scope[],
trx?: EntityManager,
loadRoles?: RoleLoader,
): Promise<string[]> {
if (!Array.isArray(scopes)) {
scopes = [scopes];
}
// Get database roles from cache
return await this.roleCacheService.getRolesWithAllScopes(namespace, scopes, trx);
return loadRoles
? await this.roleCacheService.getRolesWithAllScopes(namespace, scopes, loadRoles)
: await this.roleCacheService.getRolesWithAllScopes(namespace, scopes);
}
isRoleLicensed(role: AssignableProjectRole | AssignableGlobalRole) {
isRoleLicensed(role: string) {
// TODO: move this info into FrontendSettings
if (!isBuiltInRole(role)) {

View File

@ -33,7 +33,7 @@ import { NodeTypes } from '@/node-types';
import { PostHogClient } from '@/posthog';
import { PollJobProvider } from '@/scheduling/poll-trigger-node/poll-job-provider';
import { JwtService } from '@/services/jwt.service';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleCacheService } from '@n8n/backend-services';
import { ShutdownService } from '@/shutdown/shutdown.service';
import { TaskRunnerModule } from '@/task-runners/task-runner-module';

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { EventService, UrlService } from '@n8n/backend-services';
import { EventService, UrlService, RoleCacheService } from '@n8n/backend-services';
import { HTML_NONCE_PLACEHOLDER, LICENSE_FEATURES } from '@n8n/constants';
import {
AuthRolesService,
@ -42,7 +42,7 @@ import { Server } from '@/server';
import { JwtService } from '@/services/jwt.service';
import { ExecutionsPruningService } from '@/services/pruning/executions-pruning.service';
import { WorkflowHistoryCompactionService } from '@/services/pruning/workflow-history-compaction.service';
import { RoleCacheService } from '@/services/role-cache.service';
import { WorkflowStatisticsRollupService } from '@/services/workflow-statistics-rollup.service';
import { WaitTracker } from '@/wait-tracker';

View File

@ -1,11 +1,10 @@
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type { AuthenticatedRequest, Project } from '@n8n/db';
import type { Role } from '@n8n/permissions';
import { mock } from 'vitest-mock-extended';
import { NotFoundError } from '@n8n/errors';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import { RoleController } from '../role.controller';

View File

@ -13,7 +13,7 @@ import type {
RoleMembersResponse,
RoleProjectMembersResponse,
} from '@n8n/api-types';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { LICENSE_FEATURES } from '@n8n/constants';
import { AuthenticatedRequest } from '@n8n/db';
import {
@ -33,7 +33,6 @@ import { Role as RoleDTO } from '@n8n/permissions';
import { NotFoundError } from '@n8n/errors';
import { ProjectService } from '@/services/project.service.ee';
import { assertCanManageRoleType, canReassignUsers } from '@/services/role-authorization';
import { RoleService } from '@/services/role.service';
@RestController('/roles')
export class RoleController {

View File

@ -16,7 +16,7 @@ import type { ExternalSecretsConfig } from '@/modules/external-secrets.ee/extern
import type { SecretsProviderAccessCheckService } from '@/modules/external-secrets.ee/secret-provider-access-check.service.ee';
import type { OwnershipService } from '@/services/ownership.service';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
describe('EnterpriseCredentialsService', () => {
const sharedCredentialsRepository = mock<SharedCredentialsRepository>();

View File

@ -2,7 +2,7 @@ import { CredentialDescriptionsService } from '@/credentials/credential-descript
import type { PostHogClient } from '@/posthog';
import { CREDENTIAL_DESCRIPTION_MAX_LENGTH } from '@n8n/api-types';
import type { Logger } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type {
CredentialsRepository,
ICredentialsDb,
@ -65,7 +65,6 @@ import * as checkAccess from '@/permissions.ee/check-access';
import type { CredentialsTester } from '@/services/credentials-tester.service';
import type { OwnershipService } from '@/services/ownership.service';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import { mockExistingCredential } from './credentials.test-data';

View File

@ -11,7 +11,7 @@ import type { CredentialSharingRole, ProjectRole, Scope } from '@n8n/permissions
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
import { In } from '@n8n/typeorm';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
/**
* The credential scopes an instance role can hold without being allowed to use a

View File

@ -18,7 +18,7 @@ import { ExternalSecretsConfig } from '@/modules/external-secrets.ee/external-se
import { SecretsProviderAccessCheckService } from '@/modules/external-secrets.ee/secret-provider-access-check.service.ee';
import { OwnershipService } from '@/services/ownership.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { CredentialConnectionStatusProxy } from './credential-connection-status-proxy';
import { CredentialsFinderService } from './credentials-finder.service';

View File

@ -1,6 +1,6 @@
import type { CreateCredentialDto, CredentialConnectionStatus } from '@n8n/api-types';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { Time } from '@n8n/constants';
import {
Project,
@ -76,7 +76,6 @@ import type { CredentialRequest, ListQuery } from '@/requests';
import { CredentialsTester } from '@/services/credentials-tester.service';
import { OwnershipService } from '@/services/ownership.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { CredentialConnectionStatusProxy } from './credential-connection-status-proxy';
import { CredentialDescriptionsService } from './credential-descriptions.service';

View File

@ -3,7 +3,7 @@ import { Container } from '@n8n/di';
import { mock } from 'vitest-mock-extended';
import type { EngineDataPlaneProxyService } from '@/services/engine-data-plane-proxy.service';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { WorkflowSharingService } from '@/workflows/workflow-sharing.service';
import type { ExecutionCursor } from '../execution-cursor';

View File

@ -3,7 +3,7 @@ import { Container, Service } from '@n8n/di';
import type { Scope } from '@n8n/permissions';
import { EngineDataPlaneProxyService } from '@/services/engine-data-plane-proxy.service';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { WorkflowSharingService } from '@/workflows/workflow-sharing.service';
import { hasPosition, type ExecutionCursor } from './execution-cursor';

View File

@ -4,7 +4,7 @@ import type { InstanceSettings } from 'n8n-core';
import { mock } from 'vitest-mock-extended';
import type { Push } from '@/push';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { Publisher } from '@/scaling/pubsub/publisher.service';
import { AgentPushRecipientsService } from '../agent-push-recipients.service';

View File

@ -5,7 +5,7 @@ import { mock } from 'vitest-mock-extended';
import type { Push } from '@/push';
import type { Publisher } from '@/scaling/pubsub/publisher.service';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import { AgentPushRecipientsService } from '../agent-push-recipients.service';
import { AgentUpdateBroadcaster } from '../agent-update-broadcaster';

View File

@ -1,7 +1,7 @@
import { UserRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
@Service()
export class AgentPushRecipientsService {

View File

@ -1,6 +1,6 @@
import type { RenameDataTableColumnDto } from '@n8n/api-types';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { mockInstance, testModules } from '@n8n/backend-test-utils';
import { ProjectRelationRepository, ProjectRepository, type User } from '@n8n/db';
import { In } from '@n8n/typeorm';
@ -21,7 +21,6 @@ import { DataTableNotFoundError } from '../errors/data-table-not-found.error';
import { DataTableValidationError } from '../errors/data-table-validation.error';
import { ProjectNotFoundError, ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
describe('DataTableService', () => {
let dataTableService: DataTableService;

View File

@ -4,7 +4,7 @@ import { ProjectRelationRepository, User } from '@n8n/db';
import { Service } from '@n8n/di';
import { hasGlobalScope } from '@n8n/permissions';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { DataTableRepository } from './data-table.repository';

View File

@ -11,7 +11,7 @@ import type {
UpdateDataTableRowDto,
} from '@n8n/api-types';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { ProjectRelationRepository, ProjectRepository, type User } from '@n8n/db';
import { Service } from '@n8n/di';
import { hasGlobalScope, type Scope } from '@n8n/permissions';
@ -48,7 +48,6 @@ import { DataTableValidationError } from './errors/data-table-validation.error';
import { normalizeRows } from './utils/sql-utils';
import { ProjectNotFoundError, ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
@Service()
export class DataTableService {

View File

@ -1,6 +1,7 @@
import type { Logger } from '@n8n/backend-common';
import type {
ProjectRelationRepository,
RoleRepository,
SharedCredentialsRepository,
User,
UserRepository,
@ -9,7 +10,7 @@ import type { EntityManager } from '@n8n/typeorm';
import type { Cipher } from 'n8n-core';
import { mock } from 'vitest-mock-extended';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import { SYSTEM_RESOLVER_ID } from '../../constants';
import { DynamicCredentialUserEntry } from '../../database/entities/dynamic-credential-user-entry';
@ -37,6 +38,7 @@ describe('CredentialConnectionStatusService', () => {
const cipher = mock<Cipher>();
const logger = mock<Logger>();
const em = mock<EntityManager>();
const roleRepository = mock<RoleRepository>();
const service = new CredentialConnectionStatusService(
repository,
@ -46,6 +48,7 @@ describe('CredentialConnectionStatusService', () => {
projectRelationRepository,
cipher,
logger,
roleRepository,
);
const CRED_ID = 'cred-1';
@ -202,7 +205,7 @@ describe('CredentialConnectionStatusService', () => {
expect(roleService.rolesWithScope).toHaveBeenCalledWith(
'credential',
'credential:connect',
em,
expect.any(Function),
);
expect(sharedCredentialsRepository.findPairsWithCredentialAccess).toHaveBeenCalledWith(
[{ credentialId: CRED_ID, userId: 'sharee-1' }],

View File

@ -3,6 +3,7 @@ import {
CredentialsEntity,
In,
ProjectRelationRepository,
RoleRepository,
SharedCredentialsRepository,
UserRepository,
type User,
@ -18,7 +19,7 @@ import type {
UserConnection,
} from '@/credentials/credential-connection-status-provider.interface';
import { extractAccountIdentifierFromData } from '@/oauth/account-identifier';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { SYSTEM_RESOLVER_ID } from '../constants';
import { DynamicCredentialUserEntry } from '../database/entities/dynamic-credential-user-entry';
@ -54,6 +55,7 @@ export class CredentialConnectionStatusService implements ICredentialConnectionS
private readonly projectRelationRepository: ProjectRelationRepository,
private readonly cipher: Cipher,
private readonly logger: Logger,
private readonly roleRepository: RoleRepository,
) {}
async findMyConnections(
@ -214,7 +216,7 @@ export class CredentialConnectionStatusService implements ICredentialConnectionS
const validCredRoles = await this.roleService.rolesWithScope(
'credential',
CREDENTIAL_RETAIN_SCOPE,
em,
async () => await this.roleRepository.findAll(em),
);
return await this.sharedCredentialsRepository.findPairsWithCredentialAccess(
pairsToCheck,

View File

@ -8,7 +8,7 @@ import { mock } from 'vitest-mock-extended';
import { ForbiddenError, NotFoundError } from '@n8n/errors';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import { SecretsProviderAccessCheckService } from '../secret-provider-access-check.service.ee';

View File

@ -2,7 +2,7 @@ import { SettingsRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import { EXTERNAL_SECRETS_SYSTEM_ROLES_ENABLED_SETTING } from '@n8n/permissions';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
@Service()
export class ExternalSecretsSettingsService {

View File

@ -9,7 +9,7 @@ import { combineScopes, getAuthPrincipalScopes, hasGlobalScope } from '@n8n/perm
import { ForbiddenError, NotFoundError } from '@n8n/errors';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
@Service()
export class SecretsProviderAccessCheckService {

View File

@ -29,7 +29,7 @@ vi.mock('@n8n/ai-utilities', () => ({
import type { Logger } from '@n8n/backend-common';
import { INSTANCE_AI_NODE_USAGE_FLAG } from '@n8n/api-types';
import type { OutboundHttp } from '@n8n/backend-network';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type { GlobalConfig } from '@n8n/config';
import type {
AiBuilderTemporaryWorkflowRepository,
@ -63,7 +63,7 @@ import type { FolderService } from '@/services/folder.service';
import type { InstanceWriteAccessService } from '@/services/instance-write-access.service';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { TagService } from '@/services/tag.service';
import type { Telemetry } from '@/telemetry';
import type { WorkflowRunner } from '@/workflow-runner';

View File

@ -27,7 +27,7 @@ vi.mock('@n8n/instance-ai', async () => {
});
import type { Logger } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type { GlobalConfig } from '@n8n/config';
import { GLOBAL_MEMBER_ROLE } from '@n8n/db';
import { Container } from '@n8n/di';
@ -65,7 +65,7 @@ import type { DataTableService } from '@/modules/data-table/data-table.service';
import type { InstanceWriteAccessService } from '@/services/instance-write-access.service';
import type { NodeTypes } from '@/node-types';
import type { PolicyEnforcementService } from '@/policy/policy-enforcement.service';
import type { RoleService } from '@/services/role.service';
import type { OutboundHttp } from '@n8n/backend-network';
import type { AiGatewayService } from '@/services/ai-gateway.service';
import type { Telemetry } from '@/telemetry';

View File

@ -1827,7 +1827,7 @@ import {
} from '@n8n/errors';
import type { License } from '@/license';
import type { AiPreferenceService } from '@/services/ai-preference.service';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { OutboundHttp } from '@n8n/backend-network';
import { ModuleRegistry } from '@n8n/backend-common';

View File

@ -26,7 +26,7 @@ import {
import type { AiGatewayConfigDto } from '@n8n/api-types';
import { LicenseState, Logger, ModuleRegistry } from '@n8n/backend-common';
import { OutboundHttp } from '@n8n/backend-network';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { GlobalConfig } from '@n8n/config';
import { Time, TOOL_EXECUTOR_NODE_NAME } from '@n8n/constants';
import type { User, ExecutionSummaries, EvaluationConfig } from '@n8n/db';
@ -192,7 +192,7 @@ import { FolderService } from '@/services/folder.service';
import { InstanceWriteAccessService } from '@/services/instance-write-access.service';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { TagService } from '@/services/tag.service';
import { Telemetry } from '@/telemetry';
import { resolveBuiltinNodeDefinitionDirs } from '@/utils/node-definition-dirs';

View File

@ -11,7 +11,7 @@ import { mock } from 'vitest-mock-extended';
import type { CredentialsService } from '@/credentials/credentials.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { createFolder } from '@test-integration/db/folders';
import { createTag } from '@test-integration/db/tags';

View File

@ -6,7 +6,7 @@ import { mock } from 'vitest-mock-extended';
import { CredentialsService } from '@/credentials/credentials.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { Telemetry } from '@/telemetry';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';

View File

@ -1,5 +1,5 @@
import { LicenseState, ModuleRegistry } from '@n8n/backend-common';
import { EventService, UrlService } from '@n8n/backend-services';
import { EventService, UrlService, RoleService } from '@n8n/backend-services';
import { mockInstance, mockLogger } from '@n8n/backend-test-utils';
import { ExecutionsConfig, GlobalConfig, WorkflowsConfig } from '@n8n/config';
import { ExecutionRepository, ProjectRepository, SharedWorkflowRepository, User } from '@n8n/db';
@ -21,7 +21,7 @@ import { FolderFinderService } from '@/services/folder-finder.service';
import { FolderService } from '@/services/folder.service';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { TagService } from '@/services/tag.service';
import { Telemetry } from '@/telemetry';
import { WorkflowRunner } from '@/workflow-runner';

View File

@ -1,6 +1,6 @@
import { INSTANCE_ACTIVITY_CONTEXT_FLAG } from '@n8n/api-types';
import { LicenseState, ModuleRegistry } from '@n8n/backend-common';
import { EventService, UrlService } from '@n8n/backend-services';
import { EventService, UrlService, RoleService } from '@n8n/backend-services';
import { mockInstance, mockLogger } from '@n8n/backend-test-utils';
import { EndpointsConfig, ExecutionsConfig, GlobalConfig, WorkflowsConfig } from '@n8n/config';
import {
@ -49,7 +49,7 @@ import { FolderFinderService } from '@/services/folder-finder.service';
import { FolderService } from '@/services/folder.service';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { TagService } from '@/services/tag.service';
import { Telemetry } from '@/telemetry';
import { WorkflowRunner } from '@/workflow-runner';

View File

@ -9,7 +9,7 @@ import {
INSTANCE_ACTIVITY_CONTEXT_FLAG,
} from '@n8n/api-types';
import { LicenseState, ModuleRegistry, type Logger } from '@n8n/backend-common';
import { EventService, UrlService } from '@n8n/backend-services';
import { EventService, UrlService, RoleService } from '@n8n/backend-services';
import { mockInstance, mockLogger } from '@n8n/backend-test-utils';
import { EndpointsConfig, ExecutionsConfig, GlobalConfig, WorkflowsConfig } from '@n8n/config';
import {
@ -42,7 +42,7 @@ import { FolderFinderService } from '@/services/folder-finder.service';
import { FolderService } from '@/services/folder.service';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { TagService } from '@/services/tag.service';
import { Telemetry } from '@/telemetry';
import { WorkflowRunner } from '@/workflow-runner';

View File

@ -7,7 +7,7 @@ import {
INSTANCE_ACTIVITY_CONTEXT_FLAG,
} from '@n8n/api-types';
import { LicenseState, Logger, ModuleRegistry } from '@n8n/backend-common';
import { EventService, UrlService } from '@n8n/backend-services';
import { EventService, UrlService, RoleService } from '@n8n/backend-services';
import { ExecutionsConfig, GlobalConfig, WorkflowsConfig } from '@n8n/config';
import { ExecutionRepository, ProjectRepository, SharedWorkflowRepository, User } from '@n8n/db';
import { Container, Service } from '@n8n/di';
@ -40,7 +40,7 @@ import { FolderFinderService } from '@/services/folder-finder.service';
import { FolderService } from '@/services/folder.service';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { TagService } from '@/services/tag.service';
import { Telemetry } from '@/telemetry';
import { WorkflowRunner } from '@/workflow-runner';

View File

@ -4,7 +4,7 @@ import z from 'zod';
import type { CredentialsService } from '@/credentials/credentials.service';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { Telemetry } from '@/telemetry';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';

View File

@ -15,9 +15,7 @@ export class ProvisioningModule implements ModuleInterface {
// Register the role-deletion checker so the core `RoleService` blocks
// deleting a role still targeted by a mapping rule, without importing
// this module.
const { RoleDeletionCheckProxy } = await import(
'@/services/role-deletion-check-proxy.service.js'
);
const { RoleDeletionCheckProxy } = await import('@n8n/backend-services');
const { ProvisioningRoleDeletionChecker } = await import('./role-deletion-checker.ee.js');
Container.get(RoleDeletionCheckProxy).registerProvider(
Container.get(ProvisioningRoleDeletionChecker),

View File

@ -1,7 +1,7 @@
import { RoleMappingRuleRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import type { RoleDeletionChecker } from '@/services/role-deletion-check-proxy.service';
import type { RoleDeletionChecker } from '@n8n/backend-services';
import { ProvisioningService } from './provisioning.service.ee';

View File

@ -1,5 +1,5 @@
import type { Logger } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import {
GLOBAL_MEMBER_ROLE,
type AuthIdentity,
@ -10,7 +10,6 @@ import {
} from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import type { RoleService } from '@/services/role.service';
import type { UserService } from '@/services/user.service';
import type { TokenExchangeConfig } from '../../token-exchange.config';

View File

@ -1,5 +1,5 @@
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import {
AuthIdentity,
AuthIdentityRepository,
@ -11,7 +11,6 @@ import { Service } from '@n8n/di';
import { GLOBAL_OWNER_ROLE_SLUG, isBuiltInRole } from '@n8n/permissions';
import { createHash } from 'node:crypto';
import { RoleService } from '@/services/role.service';
import { UserService } from '@/services/user.service';
import { TokenExchangeConfig } from '../token-exchange.config';

View File

@ -9,7 +9,7 @@ import type {
import { chunkIds } from '@n8n/db';
import type { DataTableRepository } from '@/modules/data-table/data-table.repository';
import type { CredentialsFinderService } from '@/credentials/credentials-finder.service';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { mock } from 'vitest-mock-extended';

View File

@ -19,7 +19,7 @@ import { In, type FindManyOptions } from '@n8n/typeorm';
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
import { isCredSharingEnabled } from '@/constants/credential-sharing';
import { DataTableRepository } from '@/modules/data-table/data-table.repository';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { AgentUsageProviderProxy } from './agent-usage-provider-proxy.service';

View File

@ -16,7 +16,7 @@ import { WorkflowReviewAuthorizationService } from '../workflow-review-authoriza
import { NotFoundError } from '@n8n/errors';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
const requestId = 'req-1';

View File

@ -12,7 +12,7 @@ import type {
import { mock } from 'vitest-mock-extended';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { WorkflowReviewAuthorizationService } from '../workflow-review-authorization.service';

View File

@ -1,6 +1,6 @@
import type { DecideWorkflowReviewRequestDto } from '@n8n/api-types';
import type { LicenseState, Logger } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type {
DbLockService,
Project,
@ -33,7 +33,7 @@ import { WorkflowReviewStateNotifier } from '../workflow-review-state-notifier.s
import type { CollaborationService } from '@/collaboration/collaboration.service';
import { BadRequestError, ConflictError, ForbiddenError, NotFoundError } from '@n8n/errors';
import type { ProjectService } from '@/services/project.service.ee';
import type { RoleService } from '@/services/role.service';
import type { WorkflowReviewPolicyService } from '@/services/workflow-review-policy.service';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import type { WorkflowService } from '@/workflows/workflow.service';

View File

@ -3,7 +3,7 @@ import type {
GetWorkflowReviewEligibleReviewersQueryDto,
} from '@n8n/api-types';
import type { LicenseState, Logger } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import { DbLock, User } from '@n8n/db';
import type {
AuthIdentity,
@ -27,7 +27,7 @@ import { mock } from 'vitest-mock-extended';
import type { CollaborationService } from '@/collaboration/collaboration.service';
import { BadRequestError, ConflictError, ForbiddenError, NotFoundError } from '@n8n/errors';
import type { RoleService } from '@/services/role.service';
import type { WorkflowReviewPolicyService } from '@/services/workflow-review-policy.service';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import type { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service';

View File

@ -1,6 +1,6 @@
import type { UpdateWorkflowReviewRequestVersionDto } from '@n8n/api-types';
import type { LicenseState, Logger } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import { DbLock } from '@n8n/db';
import type {
DbLockService,
@ -26,7 +26,7 @@ import { mock } from 'vitest-mock-extended';
import type { CollaborationService } from '@/collaboration/collaboration.service';
import { BadRequestError, ConflictError, ForbiddenError, NotFoundError } from '@n8n/errors';
import type { RoleService } from '@/services/role.service';
import type { WorkflowReviewPolicyService } from '@/services/workflow-review-policy.service';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import type { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service';

View File

@ -21,7 +21,7 @@ import {
import { NotFoundError } from '@n8n/errors';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { resolveDecisionCapability } from './workflow-review-decision-policy';

View File

@ -5,7 +5,7 @@ import type {
WorkflowReviewEligibleReviewersList,
WorkflowReviewRequestSummary,
} from '@n8n/api-types';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import {
DbLock,
DbLockService,
@ -24,7 +24,7 @@ import {
import { Service } from '@n8n/di';
import { BadRequestError, ConflictError, NotFoundError } from '@n8n/errors';
import { RoleService } from '@/services/role.service';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service';

View File

@ -1,19 +1,22 @@
import {
GLOBAL_MEMBER_ROLE,
ProjectRepository,
RoleRepository,
SharedCredentialsRepository,
SharedWorkflowRepository,
CredentialsRepository,
type User,
type Role,
} from '@n8n/db';
import { Container } from '@n8n/di';
import { type Scope } from '@n8n/permissions';
import type { EntityManager } from '@n8n/typeorm';
import type { Mock } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
import { NotFoundError } from '@n8n/errors';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { userHasScopes } from '../check-access';
@ -27,6 +30,7 @@ describe('userHasScopes', () => {
let hasGlobalConnectAccessMock: Mock;
let roleServiceMock: Mock;
let mockQueryBuilder: any;
const roleRepository = mock<RoleRepository>();
beforeAll(() => {
findByWorkflowMock = vi.fn();
@ -92,6 +96,7 @@ describe('userHasScopes', () => {
rolesWithScope: roleServiceMock,
}),
);
Container.set(RoleRepository, roleRepository);
});
beforeEach(() => {
@ -169,6 +174,37 @@ describe('userHasScopes', () => {
});
describe('RoleService integration', () => {
it('should load roles through the supplied transaction manager', async () => {
const credentialId = 'cred123';
const entityManager = mock<EntityManager>();
const sharedCredentialsRepository = mock<SharedCredentialsRepository>();
entityManager.createQueryBuilder.mockReturnValue(mockQueryBuilder);
entityManager.getRepository.mockReturnValue(sharedCredentialsRepository);
sharedCredentialsRepository.findBy.mockResolvedValue([
{
credentialsId: credentialId,
projectId: 'projectId',
role: 'credential:custom',
},
] as never);
roleRepository.findAll.mockResolvedValue([mock<Role>({ slug: 'credential:custom' })]);
roleServiceMock.mockImplementation(
async (_namespace, _scopes, loadRoles: () => Promise<Role[]>) =>
(await loadRoles()).map(({ slug }) => slug),
);
const result = await userHasScopes(
{ id: 'userId', scopes: [], role: GLOBAL_MEMBER_ROLE } as unknown as User,
['credential:read'],
false,
{ credentialId },
entityManager,
);
expect(roleRepository.findAll).toHaveBeenCalledWith(entityManager);
expect(result).toBe(true);
});
it('should use RoleService for credential role resolution', async () => {
const credentialId = 'cred123';
const mockRoles = ['credential:owner', 'custom:credential-admin'];
@ -187,7 +223,7 @@ describe('userHasScopes', () => {
const result = await userHasScopes(user, scopes, false, { credentialId });
expect(roleServiceMock).toHaveBeenCalledWith('credential', scopes, undefined);
expect(roleServiceMock).toHaveBeenCalledWith('credential', scopes);
expect(result).toBe(true);
});
@ -209,7 +245,7 @@ describe('userHasScopes', () => {
const result = await userHasScopes(user, scopes, false, { workflowId });
expect(roleServiceMock).toHaveBeenCalledWith('workflow', scopes, undefined);
expect(roleServiceMock).toHaveBeenCalledWith('workflow', scopes);
expect(result).toBe(true);
});
@ -231,7 +267,7 @@ describe('userHasScopes', () => {
const result = await userHasScopes(user, scopes, false, { credentialId });
expect(roleServiceMock).toHaveBeenCalledWith('credential', scopes, undefined);
expect(roleServiceMock).toHaveBeenCalledWith('credential', scopes);
expect(result).toBe(true);
});
@ -273,7 +309,7 @@ describe('userHasScopes', () => {
await userHasScopes(user, ['credential:read'], false, { credentialId });
expect(roleServiceMock).toHaveBeenCalledWith('credential', ['credential:read'], undefined);
expect(roleServiceMock).toHaveBeenCalledWith('credential', ['credential:read']);
expect(roleServiceMock).not.toHaveBeenCalledWith('workflow', expect.anything());
});
@ -292,7 +328,7 @@ describe('userHasScopes', () => {
await userHasScopes(user, ['workflow:execute'], false, { workflowId });
expect(roleServiceMock).toHaveBeenCalledWith('workflow', ['workflow:execute'], undefined);
expect(roleServiceMock).toHaveBeenCalledWith('workflow', ['workflow:execute']);
expect(roleServiceMock).not.toHaveBeenCalledWith('credential', expect.anything());
});
@ -396,7 +432,7 @@ describe('userHasScopes', () => {
const result = await userHasScopes(user, scopes, false, { workflowId });
expect(roleServiceMock).toHaveBeenCalledWith('workflow', scopes, undefined);
expect(roleServiceMock).toHaveBeenCalledWith('workflow', scopes);
expect(result).toBe(true);
});
});

View File

@ -1,7 +1,7 @@
import { mockInstance } from '@n8n/backend-test-utils';
import { ProjectRelationRepository, User } from '@n8n/db';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { ProjectScopeService } from '../project-scope.service';

View File

@ -5,6 +5,7 @@ import {
CredentialsRepository,
Project,
ProjectRepository,
RoleRepository,
SharedCredentials,
SharedCredentialsRepository,
SharedWorkflow,
@ -16,7 +17,7 @@ import { UnexpectedError } from 'n8n-workflow';
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
import { NotFoundError } from '@n8n/errors';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
const INSTANCE_CREDENTIAL_MANAGEMENT_SCOPES = new Set<Scope>([
'credential:read',
@ -114,6 +115,13 @@ export async function userHasScopes(
// Then find at least one of the above qualifying projects having one of
// those resource roles over the resource being checked.
const roleService = Container.get(RoleService);
const loadRoles = entityManager
? async () => await Container.get(RoleRepository).findAll(entityManager)
: undefined;
const rolesWithScope = async (namespace: 'credential' | 'workflow') =>
loadRoles
? await roleService.rolesWithScope(namespace, scopes, loadRoles)
: await roleService.rolesWithScope(namespace, scopes);
if (credentialId) {
const credentialRepo = entityManager
@ -125,7 +133,7 @@ export async function userHasScopes(
throw new NotFoundError(`Credential with ID "${credentialId}" not found.`);
}
const validRoles = await roleService.rolesWithScope('credential', scopes, entityManager);
const validRoles = await rolesWithScope('credential');
const hasValidRoles = credentials.some(
(c) => userProjectIds.includes(c.projectId) && validRoles.includes(c.role),
@ -150,7 +158,7 @@ export async function userHasScopes(
throw new NotFoundError(`Workflow with ID "${workflowId}" not found.`);
}
const validRoles = await roleService.rolesWithScope('workflow', scopes, entityManager);
const validRoles = await rolesWithScope('workflow');
return workflows.some(
(w) => userProjectIds.includes(w.projectId) && validRoles.includes(w.role),

View File

@ -2,7 +2,7 @@ import { ProjectRelationRepository, type User } from '@n8n/db';
import { Service } from '@n8n/di';
import { hasGlobalScope, type Scope } from '@n8n/permissions';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
/**
* Resolves the project roles, or the project IDs, that restrict a scope-aware query.

View File

@ -1,11 +1,9 @@
import type { CreateRoleDto } from '@n8n/api-types';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type { AuthenticatedRequest } from '@n8n/db';
import type { Role } from '@n8n/permissions';
import { mock } from 'vitest-mock-extended';
import type { RoleService } from '@/services/role.service';
import { RolesPublicController } from '../roles.public.controller';
describe('RolesPublicController', () => {

View File

@ -8,7 +8,7 @@ import {
UpdateRolePublicDto,
roleSlugParamSchema,
} from '@n8n/api-types';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { LICENSE_FEATURES } from '@n8n/constants';
import { AuthenticatedRequest } from '@n8n/db';
import {
@ -33,7 +33,6 @@ import type { Response } from 'express';
import { NotFoundError } from '@n8n/errors';
import { assertCanManageRoleType, canReassignUsers } from '@/services/role-authorization';
import { RoleService } from '@/services/role.service';
type PublicRoleNamespace = Extract<RoleNamespace, 'global' | 'project'>;
const isPublicRole = (role: RoleDTO): role is RoleDTO & { roleType: PublicRoleNamespace } =>

View File

@ -24,7 +24,7 @@ import { mock } from 'vitest-mock-extended';
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
import { RoleService } from '../role.service';
import { RoleService } from '@n8n/backend-services';
describe('CredentialsFinderService', () => {
const roleService = mockInstance(RoleService);

View File

@ -1,7 +1,7 @@
import type { Folder, FolderRepository, User } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import { FolderFinderService } from '../folder-finder.service';

View File

@ -1,9 +1,10 @@
import type { ProjectRelation } from '@n8n/api-types';
import type { Logger, ModuleRegistry } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import {
type Project,
type ProjectRepository,
type RoleRepository,
type SharedCredentialsRepository,
type SharedWorkflowRepository,
type ProjectRelationRepository,
@ -21,7 +22,6 @@ import { mock } from 'vitest-mock-extended';
import type { OwnershipService } from '../ownership.service';
import { ProjectService } from '../project.service.ee';
import type { RoleService } from '../role.service';
import type { ICredentialConnectionStatusProvider } from '@/credentials/credential-connection-status-provider.interface';
import { BadRequestError, ForbiddenError } from '@n8n/errors';
@ -48,6 +48,7 @@ describe('ProjectService', () => {
const eventService = mock<EventService>();
const userManagementMailer = mock<UserManagementMailer>();
const userRepository = mock<UserRepository>();
const roleRepository = mock<RoleRepository>();
const user = mock<User>({ id: 'actor-user', role: mock({ slug: 'global:owner' }) });
const projectService = new ProjectService(
sharedWorkflowRepository,
@ -63,6 +64,7 @@ describe('ProjectService', () => {
eventService,
userManagementMailer,
userRepository,
roleRepository,
);
beforeEach(() => {

View File

@ -1,4 +1,4 @@
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import {
SettingsRepository,
@ -12,7 +12,7 @@ import {
} from '@n8n/permissions';
import { InstanceRedactionEnforcementService } from '@/modules/redaction/instance-redaction-enforcement.service';
import { RoleService } from '@/services/role.service';
import { SecuritySettingsService } from '@/services/security-settings.service';
describe('SecuritySettingsService', () => {

View File

@ -1,5 +1,5 @@
import { InviteUsersRequestDto } from '@n8n/api-types';
import { type EventService, UrlService } from '@n8n/backend-services';
import { type EventService, UrlService, type RoleService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import { GlobalConfig } from '@n8n/config';
import {
@ -32,7 +32,6 @@ import { JwtService } from '../jwt.service';
import type { OwnershipService } from '../ownership.service';
import type { ProjectService } from '../project.service.ee';
import type { PublicApiKeyService } from '../public-api-key.service';
import type { RoleService } from '../role.service';
describe('UserService', () => {
const globalConfig = mockInstance(GlobalConfig, {

View File

@ -5,7 +5,7 @@ import { hasGlobalScope, type Scope } from '@n8n/permissions';
import type { FindOptionsWhere } from '@n8n/typeorm';
import { In } from '@n8n/typeorm';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
/**
* Resolves folders by id for a user, enforcing access through the folder's home

View File

@ -1,6 +1,6 @@
import type { CreateProjectDto, ProjectType, UpdateProjectDto } from '@n8n/api-types';
import { LicenseState, Logger, ModuleRegistry } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import {
type User,
FolderRepository,
@ -8,6 +8,7 @@ import {
ProjectRelation,
ProjectRelationRepository,
ProjectRepository,
RoleRepository,
ProjectIdConflictError,
SharedCredentialsRepository,
SharedWorkflowRepository,
@ -37,7 +38,6 @@ import { BadRequestError, ConflictError, ForbiddenError, NotFoundError } from '@
import { UserManagementMailer } from '@/user-management/email';
import { OwnershipService } from './ownership.service';
import { RoleService } from './role.service';
const INSTANCE_ACCESS_ROLE_ERROR =
"This user has access through their instance role. Project roles can't change their access in this project.";
@ -100,6 +100,7 @@ export class ProjectService {
private readonly eventService: EventService,
private readonly userManagementMailer: UserManagementMailer,
private readonly userRepository: UserRepository,
private readonly roleRepository: RoleRepository,
) {}
private get workflowService() {
@ -911,10 +912,11 @@ export class ProjectService {
};
if (!hasGlobalScope(user, scopes, { mode: 'allOf' })) {
// Use the same EntityManager as the project lookup (including when callers pass a
// transaction manager). Otherwise role resolution can open a second pooled connection
// while a transaction already holds a connection
const projectRoles = await this.roleService.rolesWithScope('project', scopes, em);
const projectRoles = await this.roleService.rolesWithScope(
'project',
scopes,
async () => await this.roleRepository.findAll(em),
);
where = {
...where,

View File

@ -1,5 +1,5 @@
import type { RedactionFloor } from '@n8n/api-types';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import {
SettingsRepository,
SharedCredentialsRepository,
@ -15,7 +15,6 @@ import type { DistributiveOmit } from '@n8n/utils/types';
import type { RelayEventMap, UserLike } from '@/events/maps/relay.event-map';
import { InstanceRedactionEnforcementService } from '@/modules/redaction/instance-redaction-enforcement.service';
import { RoleService } from '@/services/role.service';
/**
* The writable subset of the security policy shared by the internal controller

View File

@ -1,6 +1,6 @@
import type { RoleChangeRequestDto } from '@n8n/api-types';
import { Logger } from '@n8n/backend-common';
import { EventService, UrlService } from '@n8n/backend-services';
import { EventService, UrlService, RoleService } from '@n8n/backend-services';
import { GlobalConfig } from '@n8n/config';
import type { PublicUser } from '@n8n/db';
import {
@ -34,7 +34,6 @@ import { JwtService } from './jwt.service';
import { OwnershipService } from './ownership.service';
import { ProjectService } from './project.service.ee';
import { PublicApiKeyService } from './public-api-key.service';
import { RoleService } from './role.service';
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { BadRequestError, ForbiddenError, InternalServerError, NotFoundError } from '@n8n/errors';

View File

@ -1,13 +1,15 @@
/* eslint-disable @typescript-eslint/unbound-method -- vi mocks */
import type {
FolderRepository,
RoleRepository,
SharedWorkflow,
SharedWorkflowRepository,
WorkflowRepository,
} from '@n8n/db';
import type { EntityManager } from '@n8n/typeorm';
import { mock } from 'vitest-mock-extended';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import { WorkflowFinderService } from '../workflow-finder.service';
@ -22,16 +24,43 @@ function makeService(rows?: FolderRow[]) {
const workflowRepository = mock<WorkflowRepository>();
const folderRepository = mock<FolderRepository>();
const roleService = mock<RoleService>();
const roleRepository = mock<RoleRepository>();
const service = new WorkflowFinderService(
sharedWorkflowRepository,
folderRepository,
roleService,
workflowRepository,
roleRepository,
);
return { service, sharedWorkflowRepository, workflowRepository, folderRepository, roleService };
return {
service,
sharedWorkflowRepository,
workflowRepository,
folderRepository,
roleService,
roleRepository,
};
}
describe('WorkflowFinderService', () => {
it('loads roles through the supplied transaction manager', async () => {
const { service, roleService, roleRepository } = makeService();
const em = mock<EntityManager>();
roleService.rolesWithScope.mockResolvedValue([]);
roleRepository.findAll.mockResolvedValue([]);
await service.findWorkflowForUser(
'workflow-1',
{ id: 'user-1', role: { slug: 'global:member', scopes: [] } } as never,
['workflow:read'],
{ em },
);
const loadRoles = roleService.rolesWithScope.mock.calls[0][2];
await loadRoles?.();
expect(roleRepository.findAll).toHaveBeenCalledWith(em);
});
describe('findWorkflowIdsByFolder', () => {
it('returns an empty map without querying when no folder ids are given', async () => {
const { service, sharedWorkflowRepository } = makeService();

View File

@ -2,7 +2,7 @@ import type { Logger, LicenseState } from '@n8n/backend-common';
import type { ProjectRelationRepository, SharedWorkflowRepository, UserRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import type { RoleService } from '@/services/role.service';
import type { RoleService } from '@n8n/backend-services';
import { WorkflowSharingService } from '../workflow-sharing.service';

View File

@ -1,5 +1,5 @@
import type { LicenseState } from '@n8n/backend-common';
import type { EventService } from '@n8n/backend-services';
import { type EventService, type RoleService } from '@n8n/backend-services';
import type { GlobalConfig, WorkflowsConfig } from '@n8n/config';
import type {
Project,
@ -45,7 +45,7 @@ import type { PollTriggerJobRegistrar } from '@/scheduling/poll-trigger-node/pol
import type { ScheduleTriggerJobRegistrar } from '@/scheduling/schedule-trigger-node/schedule-trigger-job-registrar';
import type { WorkflowScheduledJobOwner } from '@/scheduling/workflow-scheduled-job-owner';
import type { OwnershipService } from '@/services/ownership.service';
import type { RoleService } from '@/services/role.service';
import type { TagService } from '@/services/tag.service';
import type { WebhookService } from '@/webhooks/webhook.service';
import * as WorkflowHelpers from '@/workflow-helpers';

View File

@ -1,12 +1,18 @@
import type { SharedWorkflow, User, WorkflowEntity, ListQuery } from '@n8n/db';
import { SharedWorkflowRepository, FolderRepository, WorkflowRepository, chunkIds } from '@n8n/db';
import {
SharedWorkflowRepository,
FolderRepository,
WorkflowRepository,
RoleRepository,
chunkIds,
} from '@n8n/db';
import { Service } from '@n8n/di';
import { hasGlobalScope, type Scope } from '@n8n/permissions';
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
import { In, IsNull } from '@n8n/typeorm';
import { userHasScopes } from '@/permissions.ee/check-access';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
export type FindWorkflowsForUserOptions = {
filters?: {
@ -31,6 +37,7 @@ export class WorkflowFinderService {
private readonly folderRepository: FolderRepository,
private readonly roleService: RoleService,
private readonly workflowRepository: WorkflowRepository,
private readonly roleRepository: RoleRepository,
) {}
async findWorkflowForUser(
@ -95,10 +102,15 @@ export class WorkflowFinderService {
em?: EntityManager,
): Promise<FindOptionsWhere<SharedWorkflow>> {
if (hasGlobalScope(user, scopes, { mode: 'allOf' })) return {};
const loadRoles = em ? async () => await this.roleRepository.findAll(em) : undefined;
const rolesWithScope = async (namespace: 'project' | 'workflow') =>
loadRoles
? await this.roleService.rolesWithScope(namespace, scopes, loadRoles)
: await this.roleService.rolesWithScope(namespace, scopes);
const [projectRoles, workflowRoles] = await Promise.all([
this.roleService.rolesWithScope('project', scopes, em),
this.roleService.rolesWithScope('workflow', scopes, em),
rolesWithScope('project'),
rolesWithScope('workflow'),
]);
return {

View File

@ -11,7 +11,7 @@ import {
} from '@n8n/permissions';
import { In } from '@n8n/typeorm';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
export type ShareWorkflowOptions =
| { scopes: Scope[]; projectId?: string }

View File

@ -1,7 +1,7 @@
import { UpdateWorkflowHistoryVersionDto } from '@n8n/api-types';
import type { WorkflowListPublicationStatus } from '@n8n/api-types';
import { LicenseState, Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { EventService, RoleService } from '@n8n/backend-services';
import { GlobalConfig } from '@n8n/config';
import type { User, ListQueryDb, Project, WorkflowFolderUnionFull, WorkflowHistory } from '@n8n/db';
import {
@ -71,7 +71,7 @@ import { ScheduleTriggerJobRegistrar } from '@/scheduling/schedule-trigger-node/
import { WorkflowScheduledJobOwner } from '@/scheduling/workflow-scheduled-job-owner';
import { OwnershipService } from '@/services/ownership.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { TagService } from '@/services/tag.service';
import { WEBHOOK_CONFLICT_MESSAGE } from '@/webhooks/constants';
import { WebhookService } from '@/webhooks/webhook.service';

View File

@ -4,7 +4,7 @@ import { AuthRolesService, RoleRepository, type User } from '@n8n/db';
import { Container } from '@n8n/di';
import { PROJECT_OWNER_ROLE_SLUG } from '@n8n/permissions';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleCacheService } from '@n8n/backend-services';
import { createMemberWithApiKey } from './shared/db/users';
import * as utils from './shared/utils/';

View File

@ -2,7 +2,7 @@ import type { CreateRoleDto, UpdateRoleDto } from '@n8n/api-types';
import { mockInstance } from '@n8n/backend-test-utils';
import { type Role } from '@n8n/permissions';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { cleanupRolesAndScopes } from '../shared/db/roles';
import { createMember, createOwner } from '../shared/db/users';

View File

@ -20,7 +20,7 @@ import config from '@/config';
import { CredentialsService } from '@/credentials/credentials.service';
import { CredentialsTester } from '@/services/credentials-tester.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleCacheService } from '@n8n/backend-services';
import { SecuritySettingsService } from '@/services/security-settings.service';
import { UserManagementMailer } from '@/user-management/email';

View File

@ -472,7 +472,7 @@ describe('CredentialsRepository', () => {
const { CredentialsFinderService } = await import(
'@/credentials/credentials-finder.service.js'
);
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
const member = await createMember();
const teamProject = await createTeamProject('test-project');
@ -566,7 +566,7 @@ describe('CredentialsRepository', () => {
const { CredentialsFinderService } = await import(
'@/credentials/credentials-finder.service.js'
);
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
const member = await createMember();
const teamProject = await createTeamProject('test-project');
@ -636,7 +636,7 @@ describe('CredentialsRepository', () => {
const { CredentialsFinderService } = await import(
'@/credentials/credentials-finder.service.js'
);
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
const member = await createMember();
@ -723,7 +723,7 @@ describe('CredentialsRepository', () => {
const { CredentialsFinderService } = await import(
'@/credentials/credentials-finder.service.js'
);
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
// Create two separate users
const userA = await createMember();

View File

@ -1132,7 +1132,7 @@ describe('WorkflowRepository', () => {
const { createMember } = await import('../../shared/db/users.js');
const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils');
const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js');
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
const member = await createMember();
const teamProject = await createTeamProject('test-project');
@ -1270,7 +1270,7 @@ describe('WorkflowRepository', () => {
const { createMember } = await import('../../shared/db/users.js');
const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils');
const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js');
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
const member = await createMember();
@ -1352,7 +1352,7 @@ describe('WorkflowRepository', () => {
const { createMember } = await import('../../shared/db/users.js');
const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils');
const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js');
const { RoleService } = await import('@/services/role.service.js');
const { RoleService } = await import('@n8n/backend-services');
// Create two separate users
const userA = await createMember();

View File

@ -26,7 +26,7 @@ import { mock } from 'vitest-mock-extended';
import { CredentialsService } from '@/credentials/credentials.service';
import { ExternalSecretsConfig } from '@/modules/external-secrets.ee/external-secrets.config';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleCacheService } from '@n8n/backend-services';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { PostHogClient } from '@/posthog';
import { CredentialsTester } from '@/services/credentials-tester.service';

View File

@ -14,8 +14,7 @@ import { BadRequestError, NotFoundError } from '@n8n/errors';
import { License } from '@/license';
import { ProvisioningRoleDeletionChecker } from '@/modules/provisioning.ee/role-deletion-checker.ee';
import { ProjectService } from '@/services/project.service.ee';
import { RoleDeletionCheckProxy } from '@/services/role-deletion-check-proxy.service';
import { RoleService } from '@/services/role.service';
import { RoleDeletionCheckProxy, RoleService } from '@n8n/backend-services';
import {
createRole,

View File

@ -2,7 +2,7 @@ import { Role, RoleRepository, Scope, ScopeRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import type { Scope as ScopeType } from '@n8n/permissions';
import { RoleCacheService } from '@/services/role-cache.service';
import { RoleCacheService } from '@n8n/backend-services';
/**
* Creates a test role with given parameters

View File

@ -32,7 +32,7 @@ import { NodeTypes } from '@/node-types';
import { PolicyEnforcementService } from '@/policy/policy-enforcement.service';
import { OwnershipService } from '@/services/ownership.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { RoleService } from '@n8n/backend-services';
import { Telemetry } from '@/telemetry';
import { WebhookService } from '@/webhooks/webhook.service';
import { WorkflowHookContextService } from '@/workflow-hook-context.service';

View File

@ -1813,6 +1813,9 @@ importers:
'@n8n/constants':
specifier: workspace:*
version: link:../constants
'@n8n/db':
specifier: workspace:*
version: link:../db
'@n8n/decorators':
specifier: workspace:*
version: link:../decorators
@ -1822,6 +1825,9 @@ importers:
'@n8n/errors':
specifier: workspace:*
version: link:../errors
'@n8n/permissions':
specifier: workspace:*
version: link:../permissions
'@n8n/utils':
specifier: workspace:*
version: link:../utils