refactor(core): Remove roleId indirection (no-changelog) (#8413)
This commit is contained in:
committed by
GitHub
parent
1affebd85e
commit
d6deceacde
@@ -55,7 +55,7 @@ export class AuthController {
|
||||
const preliminaryUser = await handleEmailLogin(email, password);
|
||||
// if the user is an owner, continue with the login
|
||||
if (
|
||||
preliminaryUser?.globalRole?.name === 'owner' ||
|
||||
preliminaryUser?.role === 'global:owner' ||
|
||||
preliminaryUser?.settings?.allowSSOManualLogin
|
||||
) {
|
||||
user = preliminaryUser;
|
||||
@@ -65,7 +65,7 @@ export class AuthController {
|
||||
}
|
||||
} else if (isLdapCurrentAuthenticationMethod()) {
|
||||
const preliminaryUser = await handleEmailLogin(email, password);
|
||||
if (preliminaryUser?.globalRole?.name === 'owner') {
|
||||
if (preliminaryUser?.role === 'global:owner') {
|
||||
user = preliminaryUser;
|
||||
usedAuthenticationMethod = 'email';
|
||||
} else {
|
||||
@@ -138,7 +138,7 @@ export class AuthController {
|
||||
}
|
||||
|
||||
try {
|
||||
user = await this.userRepository.findOneOrFail({ where: {}, relations: ['globalRole'] });
|
||||
user = await this.userRepository.findOneOrFail({ where: {} });
|
||||
} catch (error) {
|
||||
throw new InternalServerError(
|
||||
'No users found in database - did you wipe the users table? Create at least one user.',
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Request } from 'express';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { RoleRepository } from '@db/repositories/role.repository';
|
||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
@@ -39,7 +37,6 @@ const tablesToTruncate = [
|
||||
'installed_packages',
|
||||
'installed_nodes',
|
||||
'user',
|
||||
'role',
|
||||
'variables',
|
||||
];
|
||||
|
||||
@@ -87,7 +84,6 @@ export class E2EController {
|
||||
|
||||
constructor(
|
||||
license: License,
|
||||
private readonly roleRepo: RoleRepository,
|
||||
private readonly settingsRepo: SettingsRepository,
|
||||
private readonly userRepo: UserRepository,
|
||||
private readonly workflowRunner: ActiveWorkflowRunner,
|
||||
@@ -148,7 +144,7 @@ export class E2EController {
|
||||
private async truncateAll() {
|
||||
for (const table of tablesToTruncate) {
|
||||
try {
|
||||
const { connection } = this.roleRepo.manager;
|
||||
const { connection } = this.settingsRepo.manager;
|
||||
await connection.query(
|
||||
`DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`,
|
||||
);
|
||||
@@ -163,27 +159,12 @@ export class E2EController {
|
||||
members: UserSetupPayload[],
|
||||
admin: UserSetupPayload,
|
||||
) {
|
||||
const roles: Array<[Role['name'], Role['scope']]> = [
|
||||
['owner', 'global'],
|
||||
['member', 'global'],
|
||||
['admin', 'global'],
|
||||
['owner', 'workflow'],
|
||||
['owner', 'credential'],
|
||||
['user', 'credential'],
|
||||
['editor', 'workflow'],
|
||||
];
|
||||
|
||||
const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }, { id: globalAdminRoleId }] =
|
||||
await this.roleRepo.save(
|
||||
roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })),
|
||||
);
|
||||
|
||||
const instanceOwner = {
|
||||
const instanceOwner = this.userRepo.create({
|
||||
id: uuid(),
|
||||
...owner,
|
||||
password: await this.passwordUtility.hash(owner.password),
|
||||
globalRoleId: globalOwnerRoleId,
|
||||
};
|
||||
role: 'global:owner',
|
||||
});
|
||||
|
||||
if (owner?.mfaSecret && owner.mfaRecoveryCodes?.length) {
|
||||
const { encryptedRecoveryCodes, encryptedSecret } =
|
||||
@@ -192,12 +173,12 @@ export class E2EController {
|
||||
instanceOwner.mfaRecoveryCodes = encryptedRecoveryCodes;
|
||||
}
|
||||
|
||||
const adminUser = {
|
||||
const adminUser = this.userRepo.create({
|
||||
id: uuid(),
|
||||
...admin,
|
||||
password: await this.passwordUtility.hash(admin.password),
|
||||
globalRoleId: globalAdminRoleId,
|
||||
};
|
||||
role: 'global:admin',
|
||||
});
|
||||
|
||||
const users = [];
|
||||
|
||||
@@ -209,7 +190,7 @@ export class E2EController {
|
||||
id: uuid(),
|
||||
...payload,
|
||||
password: await this.passwordUtility.hash(password),
|
||||
globalRoleId: globalMemberRoleId,
|
||||
role: 'global:member',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import validator from 'validator';
|
||||
|
||||
import config from '@/config';
|
||||
import { Authorized, NoAuthRequired, Post, RequireGlobalScope, RestController } from '@/decorators';
|
||||
@@ -12,12 +13,11 @@ import { isSamlLicensedAndEnabled } from '@/sso/saml/samlHelpers';
|
||||
import { PasswordUtility } from '@/services/password.utility';
|
||||
import { PostHogClient } from '@/posthog';
|
||||
import type { User } from '@/databases/entities/User';
|
||||
import validator from 'validator';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/invitations')
|
||||
@@ -91,13 +91,13 @@ export class InvitationController {
|
||||
);
|
||||
}
|
||||
|
||||
if (invite.role && !['member', 'admin'].includes(invite.role)) {
|
||||
if (invite.role && !['global:member', 'global:admin'].includes(invite.role)) {
|
||||
throw new BadRequestError(
|
||||
`Cannot invite user with invalid role: ${invite.role}. Please ensure all invitees' roles are either 'member' or 'admin'.`,
|
||||
`Cannot invite user with invalid role: ${invite.role}. Please ensure all invitees' roles are either 'global:member' or 'global:admin'.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (invite.role === 'admin' && !this.license.isAdvancedPermissionsLicensed()) {
|
||||
if (invite.role === 'global:admin' && !this.license.isAdvancedPermissionsLicensed()) {
|
||||
throw new UnauthorizedError(
|
||||
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
|
||||
);
|
||||
@@ -106,7 +106,7 @@ export class InvitationController {
|
||||
|
||||
const attributes = req.body.map(({ email, role }) => ({
|
||||
email,
|
||||
role: role ?? 'member',
|
||||
role: role ?? 'global:member',
|
||||
}));
|
||||
|
||||
const { usersInvited, usersCreated } = await this.userService.inviteUsers(req.user, attributes);
|
||||
|
||||
@@ -80,7 +80,6 @@ export class MeController {
|
||||
await this.userService.update(userId, payload);
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
where: { id: userId },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
this.logger.info('User updated successfully', { userId });
|
||||
@@ -235,7 +234,6 @@ export class MeController {
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
select: ['settings'],
|
||||
where: { id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
return user.settings;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
|
||||
@Authorized(['global', 'owner'])
|
||||
@Authorized('global:owner')
|
||||
@RestController('/owner')
|
||||
export class OwnerController {
|
||||
constructor(
|
||||
@@ -35,7 +35,7 @@ export class OwnerController {
|
||||
@Post('/setup')
|
||||
async setupOwner(req: OwnerRequest.Post, res: Response) {
|
||||
const { email, firstName, lastName, password } = req.body;
|
||||
const { id: userId, globalRole } = req.user;
|
||||
const { id: userId } = req.user;
|
||||
|
||||
if (config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
||||
this.logger.debug(
|
||||
@@ -65,17 +65,6 @@ export class OwnerController {
|
||||
throw new BadRequestError('First and last names are mandatory');
|
||||
}
|
||||
|
||||
// TODO: This check should be in a middleware outside this class
|
||||
if (globalRole.scope === 'global' && globalRole.name !== 'owner') {
|
||||
this.logger.debug(
|
||||
'Request to claim instance ownership failed because user shell does not exist or has wrong role!',
|
||||
{
|
||||
userId,
|
||||
},
|
||||
);
|
||||
throw new BadRequestError('Invalid request');
|
||||
}
|
||||
|
||||
let owner = req.user;
|
||||
|
||||
Object.assign(owner, {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { License } from '@/License';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
|
||||
@RestController('/roles')
|
||||
export class RoleController {
|
||||
constructor(
|
||||
private readonly roleService: RoleService,
|
||||
private readonly license: License,
|
||||
) {}
|
||||
|
||||
@Get('/')
|
||||
async listRoles() {
|
||||
return this.roleService.listRoles().map((role) => {
|
||||
if (role.scope === 'global' && role.name === 'admin') {
|
||||
return { ...role, isAvailable: this.license.isAdvancedPermissionsLicensed() };
|
||||
}
|
||||
|
||||
return { ...role, isAvailable: true };
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserService } from '@/services/user.service';
|
||||
import { listQueryMiddleware } from '@/middlewares';
|
||||
import { Logger } from '@/Logger';
|
||||
@@ -45,7 +44,6 @@ export class UsersController {
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
@@ -70,7 +68,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
if (filter?.isOwner) {
|
||||
for (const user of publicUsers) delete user.globalRole;
|
||||
for (const user of publicUsers) delete user.role;
|
||||
}
|
||||
|
||||
// remove computed fields (unselectable)
|
||||
@@ -92,12 +90,7 @@ export class UsersController {
|
||||
async listUsers(req: ListQuery.Request) {
|
||||
const { listQueryOptions } = req;
|
||||
|
||||
const globalOwner = await this.roleService.findGlobalOwnerRole();
|
||||
|
||||
const findManyOptions = await this.userRepository.toFindManyOptions(
|
||||
listQueryOptions,
|
||||
globalOwner.id,
|
||||
);
|
||||
const findManyOptions = await this.userRepository.toFindManyOptions(listQueryOptions);
|
||||
|
||||
const users = await this.userRepository.find(findManyOptions);
|
||||
|
||||
@@ -118,7 +111,6 @@ export class UsersController {
|
||||
async getUserPasswordResetLink(req: UserRequest.PasswordResetLink) {
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
if (!user) {
|
||||
throw new NotFoundError('User not found');
|
||||
@@ -140,7 +132,6 @@ export class UsersController {
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
select: ['settings'],
|
||||
where: { id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
return user.settings;
|
||||
@@ -194,11 +185,6 @@ export class UsersController {
|
||||
telemetryData.migration_user_id = transferId;
|
||||
}
|
||||
|
||||
const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([
|
||||
this.roleService.findWorkflowOwnerRole(),
|
||||
this.roleService.findCredentialOwnerRole(),
|
||||
]);
|
||||
|
||||
if (transferId) {
|
||||
const transferee = users.find((user) => user.id === transferId);
|
||||
|
||||
@@ -208,7 +194,7 @@ export class UsersController {
|
||||
.getRepository(SharedWorkflow)
|
||||
.find({
|
||||
select: ['workflowId'],
|
||||
where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'workflow:owner' },
|
||||
})
|
||||
.then((sharedWorkflows) => sharedWorkflows.map(({ workflowId }) => workflowId));
|
||||
|
||||
@@ -223,7 +209,7 @@ export class UsersController {
|
||||
// Transfer ownership of owned workflows
|
||||
await transactionManager.update(
|
||||
SharedWorkflow,
|
||||
{ user: userToDelete, role: workflowOwnerRole },
|
||||
{ user: userToDelete, role: 'workflow:owner' },
|
||||
{ user: transferee },
|
||||
);
|
||||
|
||||
@@ -234,7 +220,7 @@ export class UsersController {
|
||||
.getRepository(SharedCredentials)
|
||||
.find({
|
||||
select: ['credentialsId'],
|
||||
where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'credential:owner' },
|
||||
})
|
||||
.then((sharedCredentials) => sharedCredentials.map(({ credentialsId }) => credentialsId));
|
||||
|
||||
@@ -249,7 +235,7 @@ export class UsersController {
|
||||
// Transfer ownership of owned credentials
|
||||
await transactionManager.update(
|
||||
SharedCredentials,
|
||||
{ user: userToDelete, role: credentialOwnerRole },
|
||||
{ user: userToDelete, role: 'credential:owner' },
|
||||
{ user: transferee },
|
||||
);
|
||||
|
||||
@@ -271,11 +257,11 @@ export class UsersController {
|
||||
const [ownedSharedWorkflows, ownedSharedCredentials] = await Promise.all([
|
||||
this.sharedWorkflowRepository.find({
|
||||
relations: ['workflow'],
|
||||
where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'workflow:owner' },
|
||||
}),
|
||||
this.sharedCredentialsRepository.find({
|
||||
relations: ['credentials'],
|
||||
where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'credential:owner' },
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -318,23 +304,20 @@ export class UsersController {
|
||||
|
||||
const targetUser = await this.userRepository.findOne({
|
||||
where: { id: req.params.id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
if (targetUser === null) {
|
||||
throw new NotFoundError(NO_USER);
|
||||
}
|
||||
|
||||
if (req.user.globalRole.name === 'admin' && targetUser.globalRole.name === 'owner') {
|
||||
if (req.user.role === 'global:admin' && targetUser.role === 'global:owner') {
|
||||
throw new UnauthorizedError(NO_ADMIN_ON_OWNER);
|
||||
}
|
||||
|
||||
if (req.user.globalRole.name === 'owner' && targetUser.globalRole.name === 'owner') {
|
||||
if (req.user.role === 'global:owner' && targetUser.role === 'global:owner') {
|
||||
throw new UnauthorizedError(NO_OWNER_ON_OWNER);
|
||||
}
|
||||
|
||||
const roleToSet = await this.roleService.findCached('global', payload.newRoleName);
|
||||
|
||||
await this.userService.update(targetUser.id, { globalRoleId: roleToSet.id });
|
||||
await this.userService.update(targetUser.id, { role: payload.newRoleName });
|
||||
|
||||
void this.internalHooks.onUserRoleChange({
|
||||
user: req.user,
|
||||
|
||||
Reference in New Issue
Block a user