mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-18 05:59:42 +02:00
207 lines
5.7 KiB
TypeScript
207 lines
5.7 KiB
TypeScript
import {
|
|
CreateRoleDto,
|
|
RoleAssignmentsResponseDto,
|
|
RoleDeleteQueryDto,
|
|
RoleGetQueryDto,
|
|
RoleListQueryDto,
|
|
RoleMembersResponseDto,
|
|
RoleProjectMembersResponseDto,
|
|
UpdateRoleDto,
|
|
} from '@n8n/api-types';
|
|
import type {
|
|
RoleAssignmentsResponse,
|
|
RoleMembersResponse,
|
|
RoleProjectMembersResponse,
|
|
} from '@n8n/api-types';
|
|
import { EventService, RoleService } from '@n8n/backend-services';
|
|
import { LICENSE_FEATURES } from '@n8n/constants';
|
|
import { AuthenticatedRequest } from '@n8n/db';
|
|
import {
|
|
Body,
|
|
Delete,
|
|
Get,
|
|
GlobalScope,
|
|
Licensed,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
RestController,
|
|
} from '@n8n/decorators';
|
|
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';
|
|
|
|
@RestController('/roles')
|
|
export class RoleController {
|
|
constructor(
|
|
private readonly roleService: RoleService,
|
|
private readonly eventService: EventService,
|
|
private readonly projectService: ProjectService,
|
|
) {}
|
|
|
|
@Get('/')
|
|
async getAllRoles(
|
|
_req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Query query: RoleListQueryDto,
|
|
): Promise<Record<string, RoleDTO[]>> {
|
|
const allRoles = await this.roleService.getAllRoles(query.withUsageCount);
|
|
return {
|
|
global: allRoles.filter((r) => r.roleType === 'global'),
|
|
project: allRoles.filter((r) => r.roleType === 'project'),
|
|
credential: allRoles.filter((r) => r.roleType === 'credential'),
|
|
workflow: allRoles.filter((r) => r.roleType === 'workflow'),
|
|
};
|
|
}
|
|
|
|
@Get('/:slug/assignments/:projectId/members')
|
|
async getRoleProjectMembers(
|
|
req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Param('slug') slug: string,
|
|
@Param('projectId') projectId: string,
|
|
): Promise<RoleProjectMembersResponse> {
|
|
const role = await this.roleService.getRole(slug);
|
|
assertCanManageRoleType({
|
|
roleType: role.roleType,
|
|
user: req.user,
|
|
});
|
|
|
|
// Managing a role is an instance-wide capability, but the identities of a project's
|
|
// members are project data. Gate them on the same `project:list` check the sibling
|
|
// user and project routes use, so a role manager only sees projects it can already see.
|
|
const project = await this.projectService.getProjectWithScope(req.user, projectId, [
|
|
'project:list',
|
|
]);
|
|
if (!project) {
|
|
throw new NotFoundError('Project not found');
|
|
}
|
|
|
|
const result = await this.roleService.getRoleProjectMembers(slug, projectId);
|
|
return RoleProjectMembersResponseDto.parse(result);
|
|
}
|
|
|
|
@Get('/:slug/assignments')
|
|
async getRoleAssignments(
|
|
req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Param('slug') slug: string,
|
|
): Promise<RoleAssignmentsResponse> {
|
|
const role = await this.roleService.getRole(slug);
|
|
assertCanManageRoleType({
|
|
roleType: role.roleType,
|
|
user: req.user,
|
|
});
|
|
const result = await this.roleService.getRoleAssignments(slug);
|
|
|
|
if (result.projects.length === 0) {
|
|
return RoleAssignmentsResponseDto.parse(result);
|
|
}
|
|
|
|
// `projects` only names the projects the caller can already see. `totalProjects` stays
|
|
// the instance-wide count so the assignments tab can say "showing 1 of 3" rather than
|
|
// claim the role is unassigned; a bare number names no project and no member. The
|
|
// delete-impact warning is a different field — `usedByProjects`, served by `getRole`.
|
|
const visibleProjectIds = new Set(
|
|
await this.projectService.getProjectIdsWithScope(
|
|
req.user,
|
|
['project:list'],
|
|
result.projects.map((project) => project.projectId),
|
|
),
|
|
);
|
|
|
|
return RoleAssignmentsResponseDto.parse({
|
|
...result,
|
|
projects: result.projects.filter((project) => visibleProjectIds.has(project.projectId)),
|
|
});
|
|
}
|
|
|
|
@Get('/:slug/members')
|
|
@GlobalScope('role:read')
|
|
async getRoleMembers(
|
|
_req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Param('slug') slug: string,
|
|
): Promise<RoleMembersResponse> {
|
|
const result = await this.roleService.getRoleMembers(slug);
|
|
return RoleMembersResponseDto.parse(result);
|
|
}
|
|
|
|
@Get('/:slug')
|
|
async getRoleBySlug(
|
|
_req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Param('slug') slug: string,
|
|
@Query query: RoleGetQueryDto,
|
|
): Promise<RoleDTO> {
|
|
return await this.roleService.getRole(slug, query.withUsageCount);
|
|
}
|
|
|
|
@Patch('/:slug')
|
|
@Licensed(LICENSE_FEATURES.CUSTOM_ROLES)
|
|
async updateRole(
|
|
req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Param('slug') slug: string,
|
|
@Body updateRole: UpdateRoleDto,
|
|
): Promise<RoleDTO> {
|
|
const role = await this.roleService.getRole(slug);
|
|
assertCanManageRoleType({
|
|
roleType: role.roleType,
|
|
user: req.user,
|
|
});
|
|
return await this.roleService.updateCustomRole({
|
|
slug,
|
|
newRole: updateRole,
|
|
userId: req.user.id,
|
|
});
|
|
}
|
|
|
|
@Delete('/:slug')
|
|
@Licensed(LICENSE_FEATURES.CUSTOM_ROLES)
|
|
async deleteRole(
|
|
req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Param('slug') slug: string,
|
|
@Query query: RoleDeleteQueryDto,
|
|
): Promise<RoleDTO> {
|
|
const role = await this.roleService.getRole(slug);
|
|
assertCanManageRoleType({
|
|
roleType: role.roleType,
|
|
user: req.user,
|
|
});
|
|
const reassignRoleSlug = canReassignUsers({ role, user: req.user })
|
|
? query.reassignRoleSlug
|
|
: undefined;
|
|
return await this.roleService.removeCustomRole({
|
|
slug,
|
|
reassignRoleSlug,
|
|
userId: req.user.id,
|
|
});
|
|
}
|
|
|
|
@Post('/')
|
|
@Licensed(LICENSE_FEATURES.CUSTOM_ROLES)
|
|
async createRole(
|
|
req: AuthenticatedRequest,
|
|
_res: Response,
|
|
@Body createRole: CreateRoleDto,
|
|
): Promise<RoleDTO> {
|
|
assertCanManageRoleType({
|
|
roleType: createRole.roleType,
|
|
user: req.user,
|
|
});
|
|
const result = await this.roleService.createCustomRole(createRole);
|
|
this.eventService.emit('custom-role-created', {
|
|
userId: req.user.id,
|
|
roleSlug: result.slug,
|
|
scopes: result.scopes,
|
|
source: 'ui',
|
|
});
|
|
return result;
|
|
}
|
|
}
|