mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(core): Allow user role modification (#7797)
https://linear.app/n8n/issue/PAY-985 ``` PATCH /users/:id/role unauthenticated user ✓ should receive 401 (349 ms) member ✓ should fail to demote owner to member (349 ms) ✓ should fail to demote owner to admin (359 ms) ✓ should fail to demote admin to member (381 ms) ✓ should fail to promote other member to owner (353 ms) ✓ should fail to promote other member to admin (377 ms) ✓ should fail to promote self to admin (354 ms) ✓ should fail to promote self to owner (371 ms) admin ✓ should receive 400 on invalid payload (351 ms) ✓ should receive 404 on unknown target user (351 ms) ✓ should fail to demote owner to admin (349 ms) ✓ should fail to demote owner to member (347 ms) ✓ should fail to promote member to owner (384 ms) ✓ should fail to promote admin to owner (350 ms) ✓ should be able to demote admin to member (354 ms) ✓ should be able to demote self to member (350 ms) ✓ should be able to promote member to admin (349 ms) owner ✓ should be able to promote member to admin (349 ms) ✓ should be able to demote admin to member (349 ms) ✓ should fail to demote self to admin (348 ms) ✓ should fail to demote self to member (354 ms) ```
This commit is contained in:
@@ -4,7 +4,7 @@ import { User } from '@db/entities/User';
|
||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { Authorized, Delete, Get, RestController, Patch } from '@/decorators';
|
||||
import { BadRequestError, NotFoundError } from '@/ResponseHelper';
|
||||
import { BadRequestError, NotFoundError, UnauthorizedError } from '@/ResponseHelper';
|
||||
import { ListQuery, UserRequest, UserSettingsUpdatePayload } from '@/requests';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
import { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces';
|
||||
@@ -18,7 +18,7 @@ import { UserService } from '@/services/user.service';
|
||||
import { listQueryMiddleware } from '@/middlewares';
|
||||
import { Logger } from '@/Logger';
|
||||
|
||||
@Authorized(['global', 'owner'])
|
||||
@Authorized()
|
||||
@RestController('/users')
|
||||
export class UsersController {
|
||||
constructor(
|
||||
@@ -32,6 +32,18 @@ export class UsersController {
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
static ERROR_MESSAGES = {
|
||||
CHANGE_ROLE: {
|
||||
NO_MEMBER: 'Member cannot change role for any user',
|
||||
MISSING_NEW_ROLE_KEY: 'Expected `newRole` to exist',
|
||||
MISSING_NEW_ROLE_VALUE: 'Expected `newRole` to have `name` and `scope`',
|
||||
NO_USER: 'Target user not found',
|
||||
NO_ADMIN_ON_OWNER: 'Admin cannot change role on global owner',
|
||||
NO_OWNER_ON_OWNER: 'Owner cannot change role on global owner',
|
||||
NO_ADMIN_TO_OWNER: 'Admin cannot promote user to global owner',
|
||||
},
|
||||
} as const;
|
||||
|
||||
private async toFindManyOptions(listQueryOptions?: ListQuery.Options) {
|
||||
const findManyOptions: FindManyOptions<User> = {};
|
||||
|
||||
@@ -70,7 +82,7 @@ export class UsersController {
|
||||
return findManyOptions;
|
||||
}
|
||||
|
||||
removeSupplementaryFields(
|
||||
private removeSupplementaryFields(
|
||||
publicUsers: Array<Partial<PublicUser>>,
|
||||
listQueryOptions: ListQuery.Options,
|
||||
) {
|
||||
@@ -152,6 +164,7 @@ export class UsersController {
|
||||
/**
|
||||
* Delete a user. Optionally, designate a transferee for their workflows and credentials.
|
||||
*/
|
||||
@Authorized(['global', 'owner'])
|
||||
@Delete('/:id')
|
||||
async deleteUser(req: UserRequest.Delete) {
|
||||
const { id: idToDelete } = req.params;
|
||||
@@ -306,4 +319,75 @@ export class UsersController {
|
||||
await this.externalHooks.run('user.deleted', [await this.userService.toPublic(userToDelete)]);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// @TODO: Add scope check `@RequireGlobalScope('user:changeRole')`
|
||||
// once this has been merged: https://github.com/n8n-io/n8n/pull/7737
|
||||
@Authorized('any')
|
||||
@Patch('/:id/role')
|
||||
async changeRole(req: UserRequest.ChangeRole) {
|
||||
const {
|
||||
NO_MEMBER,
|
||||
MISSING_NEW_ROLE_KEY,
|
||||
MISSING_NEW_ROLE_VALUE,
|
||||
NO_ADMIN_ON_OWNER,
|
||||
NO_ADMIN_TO_OWNER,
|
||||
NO_USER,
|
||||
NO_OWNER_ON_OWNER,
|
||||
} = UsersController.ERROR_MESSAGES.CHANGE_ROLE;
|
||||
|
||||
if (req.user.globalRole.scope === 'global' && req.user.globalRole.name === 'member') {
|
||||
throw new UnauthorizedError(NO_MEMBER);
|
||||
}
|
||||
|
||||
const { newRole } = req.body;
|
||||
|
||||
if (!newRole) {
|
||||
throw new BadRequestError(MISSING_NEW_ROLE_KEY);
|
||||
}
|
||||
|
||||
if (!newRole.name || !newRole.scope) {
|
||||
throw new BadRequestError(MISSING_NEW_ROLE_VALUE);
|
||||
}
|
||||
|
||||
if (
|
||||
req.user.globalRole.scope === 'global' &&
|
||||
req.user.globalRole.name === 'admin' &&
|
||||
newRole.scope === 'global' &&
|
||||
newRole.name === 'owner'
|
||||
) {
|
||||
throw new UnauthorizedError(NO_ADMIN_TO_OWNER);
|
||||
}
|
||||
|
||||
const targetUser = await this.userService.findOne({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
|
||||
if (targetUser === null) {
|
||||
throw new NotFoundError(NO_USER);
|
||||
}
|
||||
|
||||
if (
|
||||
req.user.globalRole.scope === 'global' &&
|
||||
req.user.globalRole.name === 'admin' &&
|
||||
targetUser.globalRole.scope === 'global' &&
|
||||
targetUser.globalRole.name === 'owner'
|
||||
) {
|
||||
throw new UnauthorizedError(NO_ADMIN_ON_OWNER);
|
||||
}
|
||||
|
||||
if (
|
||||
req.user.globalRole.scope === 'global' &&
|
||||
req.user.globalRole.name === 'owner' &&
|
||||
targetUser.globalRole.scope === 'global' &&
|
||||
targetUser.globalRole.name === 'owner'
|
||||
) {
|
||||
throw new UnauthorizedError(NO_OWNER_ON_OWNER);
|
||||
}
|
||||
|
||||
const roleToSet = await this.roleService.findCached(newRole.scope, newRole.name);
|
||||
|
||||
await this.userService.update(targetUser.id, { globalRole: roleToSet });
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user