refactor(core): Remove roleId indirection (no-changelog) (#8413)
This commit is contained in:
committed by
GitHub
parent
1affebd85e
commit
d6deceacde
@@ -4,16 +4,13 @@ import { type INode, type INodeCredentialsDetails } from 'n8n-workflow';
|
||||
|
||||
import { Logger } from '@/Logger';
|
||||
import * as Db from '@/Db';
|
||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||
import { TagRepository } from '@/databases/repositories/tag.repository';
|
||||
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import { TagRepository } from '@db/repositories/tag.repository';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
|
||||
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
||||
import { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping';
|
||||
|
||||
import type { TagEntity } from '@/databases/entities/TagEntity';
|
||||
import type { Role } from '@/databases/entities/Role';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping';
|
||||
import type { TagEntity } from '@db/entities/TagEntity';
|
||||
import type { ICredentialsDb } from '@/Interfaces';
|
||||
|
||||
@Service()
|
||||
@@ -22,19 +19,15 @@ export class ImportService {
|
||||
|
||||
private dbTags: TagEntity[] = [];
|
||||
|
||||
private workflowOwnerRole: Role;
|
||||
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly credentialsRepository: CredentialsRepository,
|
||||
private readonly tagRepository: TagRepository,
|
||||
private readonly roleService: RoleService,
|
||||
) {}
|
||||
|
||||
async initRecords() {
|
||||
this.dbCredentials = await this.credentialsRepository.find();
|
||||
this.dbTags = await this.tagRepository.find();
|
||||
this.workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
}
|
||||
|
||||
async importWorkflows(workflows: WorkflowEntity[], userId: string) {
|
||||
@@ -64,7 +57,7 @@ export class ImportService {
|
||||
|
||||
const workflowId = upsertResult.identifiers.at(0)?.id as string;
|
||||
|
||||
await tx.upsert(SharedWorkflow, { workflowId, userId, roleId: this.workflowOwnerRole.id }, [
|
||||
await tx.upsert(SharedWorkflow, { workflowId, userId, role: 'workflow:owner' }, [
|
||||
'workflowId',
|
||||
'userId',
|
||||
]);
|
||||
|
||||
@@ -2,17 +2,14 @@ import { Service } from 'typedi';
|
||||
import { CacheService } from '@/services/cache/cache.service';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { RoleService } from './role.service';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class OwnershipService {
|
||||
constructor(
|
||||
private cacheService: CacheService,
|
||||
private userRepository: UserRepository,
|
||||
private roleService: RoleService,
|
||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
) {}
|
||||
|
||||
@@ -27,13 +24,9 @@ export class OwnershipService {
|
||||
|
||||
if (cachedValue) return this.userRepository.create(cachedValue);
|
||||
|
||||
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
|
||||
if (!workflowOwnerRole) throw new ApplicationError('Failed to find workflow owner role');
|
||||
|
||||
const sharedWorkflow = await this.sharedWorkflowRepository.findOneOrFail({
|
||||
where: { workflowId, roleId: workflowOwnerRole.id },
|
||||
relations: ['user', 'user.globalRole'],
|
||||
where: { workflowId, role: 'workflow:owner' },
|
||||
relations: ['user'],
|
||||
});
|
||||
|
||||
void this.cacheService.setHash('workflow-ownership', { [workflowId]: sharedWorkflow.user });
|
||||
@@ -61,7 +54,7 @@ export class OwnershipService {
|
||||
shared?.forEach(({ user, role }) => {
|
||||
const { id, email, firstName, lastName } = user;
|
||||
|
||||
if (role.name === 'owner') {
|
||||
if (role === 'credential:owner' || role === 'workflow:owner') {
|
||||
entity.ownedBy = { id, email, firstName, lastName };
|
||||
} else {
|
||||
entity.sharedWith.push({ id, email, firstName, lastName });
|
||||
@@ -72,11 +65,8 @@ export class OwnershipService {
|
||||
}
|
||||
|
||||
async getInstanceOwner() {
|
||||
const globalOwnerRole = await this.roleService.findGlobalOwnerRole();
|
||||
|
||||
return await this.userRepository.findOneOrFail({
|
||||
where: { globalRoleId: globalOwnerRole.id },
|
||||
relations: ['globalRole'],
|
||||
where: { role: 'global:owner' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import { Service } from 'typedi';
|
||||
import { RoleRepository } from '@db/repositories/role.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { CacheService } from '@/services/cache/cache.service';
|
||||
import type { RoleNames, RoleScopes } from '@db/entities/Role';
|
||||
import { InvalidRoleError } from '@/errors/invalid-role.error';
|
||||
import { License } from '@/License';
|
||||
|
||||
@Service()
|
||||
export class RoleService {
|
||||
constructor(
|
||||
private roleRepository: RoleRepository,
|
||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private cacheService: CacheService,
|
||||
private readonly license: License,
|
||||
) {
|
||||
void this.populateCache();
|
||||
}
|
||||
|
||||
async populateCache() {
|
||||
const allRoles = await this.roleRepository.find({});
|
||||
|
||||
if (!allRoles) return;
|
||||
|
||||
void this.cacheService.setMany(allRoles.map((r) => [r.cacheKey, r]));
|
||||
}
|
||||
|
||||
async findCached(scope: RoleScopes, name: RoleNames) {
|
||||
const cacheKey = `role:${scope}:${name}`;
|
||||
|
||||
const cachedRole = await this.cacheService.get(cacheKey);
|
||||
|
||||
if (cachedRole) return this.roleRepository.create(cachedRole);
|
||||
|
||||
let dbRole = await this.roleRepository.findRole(scope, name);
|
||||
|
||||
if (dbRole === null) {
|
||||
if (!this.isValid(scope, name)) {
|
||||
throw new InvalidRoleError(`${scope}:${name} is not a valid role`);
|
||||
}
|
||||
|
||||
const toSave = this.roleRepository.create({ scope, name });
|
||||
dbRole = await this.roleRepository.save(toSave);
|
||||
}
|
||||
|
||||
void this.cacheService.set(cacheKey, dbRole);
|
||||
|
||||
return dbRole;
|
||||
}
|
||||
|
||||
private roles: Array<{ name: RoleNames; scope: RoleScopes }> = [
|
||||
{ scope: 'global', name: 'owner' },
|
||||
{ scope: 'global', name: 'member' },
|
||||
{ scope: 'global', name: 'admin' },
|
||||
{ scope: 'workflow', name: 'owner' },
|
||||
{ scope: 'credential', name: 'owner' },
|
||||
{ scope: 'credential', name: 'user' },
|
||||
{ scope: 'workflow', name: 'editor' },
|
||||
];
|
||||
|
||||
listRoles() {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
private isValid(scope: RoleScopes, name: RoleNames) {
|
||||
return this.roles.some((r) => r.scope === scope && r.name === name);
|
||||
}
|
||||
|
||||
async findGlobalOwnerRole() {
|
||||
return await this.findCached('global', 'owner');
|
||||
}
|
||||
|
||||
async findGlobalMemberRole() {
|
||||
return await this.findCached('global', 'member');
|
||||
}
|
||||
|
||||
async findGlobalAdminRole() {
|
||||
return await this.findCached('global', 'admin');
|
||||
}
|
||||
|
||||
async findWorkflowOwnerRole() {
|
||||
return await this.findCached('workflow', 'owner');
|
||||
}
|
||||
|
||||
async findWorkflowEditorRole() {
|
||||
return await this.findCached('workflow', 'editor');
|
||||
}
|
||||
|
||||
async findCredentialOwnerRole() {
|
||||
return await this.findCached('credential', 'owner');
|
||||
}
|
||||
|
||||
async findCredentialUserRole() {
|
||||
return await this.findCached('credential', 'user');
|
||||
}
|
||||
|
||||
async findRoleByUserAndWorkflow(userId: string, workflowId: string) {
|
||||
return await this.sharedWorkflowRepository
|
||||
.findOne({
|
||||
where: { workflowId, userId },
|
||||
relations: ['role'],
|
||||
})
|
||||
.then((shared) => shared?.role);
|
||||
}
|
||||
|
||||
async findCredentialOwnerRoleId() {
|
||||
return this.license.isSharingEnabled() ? undefined : (await this.findCredentialOwnerRole()).id;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Container, Service } from 'typedi';
|
||||
import { User } from '@db/entities/User';
|
||||
import { type AssignableRole, User } from '@db/entities/User';
|
||||
import type { IUserSettings } from 'n8n-workflow';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import type { PublicUser } from '@/Interfaces';
|
||||
@@ -10,7 +10,6 @@ import { Logger } from '@/Logger';
|
||||
import { createPasswordSha } from '@/auth/jwt';
|
||||
import { UserManagementMailer } from '@/UserManagement/email';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UrlService } from '@/services/url.service';
|
||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
import type { UserRequest } from '@/requests';
|
||||
@@ -23,7 +22,6 @@ export class UserService {
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly mailer: UserManagementMailer,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly urlService: UrlService,
|
||||
) {}
|
||||
|
||||
@@ -73,7 +71,7 @@ export class UserService {
|
||||
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: decodedToken.sub },
|
||||
relations: ['authIdentities', 'globalRole'],
|
||||
relations: ['authIdentities'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -162,7 +160,7 @@ export class UserService {
|
||||
private async sendEmails(
|
||||
owner: User,
|
||||
toInviteUsers: { [key: string]: string },
|
||||
role: 'member' | 'admin',
|
||||
role: AssignableRole,
|
||||
) {
|
||||
const domain = this.urlService.getInstanceBaseUrl();
|
||||
|
||||
@@ -224,9 +222,7 @@ export class UserService {
|
||||
);
|
||||
}
|
||||
|
||||
async inviteUsers(owner: User, attributes: Array<{ email: string; role: 'member' | 'admin' }>) {
|
||||
const memberRole = await this.roleService.findGlobalMemberRole();
|
||||
const adminRole = await this.roleService.findGlobalAdminRole();
|
||||
async inviteUsers(owner: User, attributes: Array<{ email: string; role: AssignableRole }>) {
|
||||
const emails = attributes.map(({ email }) => email);
|
||||
|
||||
const existingUsers = await this.userRepository.findManyByEmail(emails);
|
||||
@@ -250,10 +246,7 @@ export class UserService {
|
||||
async (transactionManager) =>
|
||||
await Promise.all(
|
||||
toCreateUsers.map(async ({ email, role }) => {
|
||||
const newUser = Object.assign(new User(), {
|
||||
email,
|
||||
globalRole: role === 'member' ? memberRole : adminRole,
|
||||
});
|
||||
const newUser = transactionManager.create(User, { email, role });
|
||||
const savedUser = await transactionManager.save<User>(newUser);
|
||||
createdUsers.set(email, savedUser.id);
|
||||
return savedUser;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { In } from 'typeorm';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserService } from '@/services/user.service';
|
||||
|
||||
@Service()
|
||||
@@ -12,7 +11,6 @@ export class UserOnboardingService {
|
||||
constructor(
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
@@ -24,12 +22,11 @@ export class UserOnboardingService {
|
||||
let belowThreshold = true;
|
||||
const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote'];
|
||||
|
||||
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
const ownedWorkflowsIds = await this.sharedWorkflowRepository
|
||||
.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
roleId: workflowOwnerRole?.id,
|
||||
role: 'workflow:owner',
|
||||
},
|
||||
select: ['workflowId'],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user