roles update

This commit is contained in:
lendry
2026-06-25 15:14:50 +03:00
parent 9671fe458b
commit 1c55c871fc
21 changed files with 721 additions and 93 deletions

View File

@@ -21,7 +21,7 @@ import { ChatController } from './controllers/chat.controller';
import { NotificationsController } from './controllers/notifications.controller';
import { MediaController } from './controllers/media.controller';
import { CoreGrpcService } from './core-grpc.service';
import { AdminGuard, SuperAdminGuard } from './guards/admin.guard';
import { AdminGuard, RbacManageGuard, SuperAdminGuard } from './guards/admin.guard';
@Module({
imports: [
@@ -56,6 +56,6 @@ import { AdminGuard, SuperAdminGuard } from './guards/admin.guard';
])
],
controllers: [AuthController, AdminController, RbacController, SecurityController, SettingsController, PublicSettingsController, HealthController, ProfileController, DocumentsController, AddressesController, OAuthController, WellKnownController, OAuthAuthorizeDiscoveryController, AdvancedAuthController, FamilyController, ChatController, NotificationsController, MediaController],
providers: [CoreGrpcService, AdminGuard, SuperAdminGuard]
providers: [CoreGrpcService, AdminGuard, RbacManageGuard, SuperAdminGuard]
})
export class AppModule {}

View File

@@ -3,8 +3,22 @@ import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs
import { map } from 'rxjs';
import { CoreGrpcService } from '../core-grpc.service';
import { CurrentAdmin } from '../decorators/current-admin.decorator';
import { AssignUserRoleDto, CreateOAuthClientDto, CreateRoleDto, UpdateOAuthClientDto } from '../dto/rbac.dto';
import { AdminGuard, AdminRequestUser, assertAdminAnyPermission, assertAdminPermission, SuperAdminGuard } from '../guards/admin.guard';
import {
AssignUserPermissionDto,
AssignUserRoleDto,
CreateOAuthClientDto,
CreateRoleDto,
UpdateOAuthClientDto,
UpdateRoleDto
} from '../dto/rbac.dto';
import {
AdminGuard,
AdminRequestUser,
assertAdminAnyPermission,
assertAdminPermission,
RbacManageGuard,
SuperAdminGuard
} from '../guards/admin.guard';
@ApiTags('RBAC и OAuth')
@ApiBearerAuth()
@@ -30,7 +44,7 @@ export class RbacController {
}
@Get('permissions')
@UseGuards(SuperAdminGuard)
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Список прав', description: 'Возвращает все доступные permissions.' })
listPermissions() {
return this.core.rbac.ListPermissions({});
@@ -51,16 +65,33 @@ export class RbacController {
}
@Post('roles')
@UseGuards(SuperAdminGuard)
@ApiOperation({ summary: 'Создать роль', description: 'Создаёт новую роль с набором прав. Только супер-администратор.' })
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Создать роль', description: 'Создаёт новую роль с набором прав.' })
@ApiBody({ type: CreateRoleDto })
createRole(@Body() dto: CreateRoleDto, @CurrentAdmin() admin: AdminRequestUser) {
return this.core.rbac.CreateRole({ actorUserId: admin.id, ...dto });
}
@Patch('roles/:roleSlug')
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Обновить роль', description: 'Изменяет название, описание и права роли.' })
@ApiParam({ name: 'roleSlug', description: 'Slug роли' })
@ApiBody({ type: UpdateRoleDto })
updateRole(@Param('roleSlug') roleSlug: string, @Body() dto: UpdateRoleDto, @CurrentAdmin() admin: AdminRequestUser) {
return this.core.rbac.UpdateRole({ actorUserId: admin.id, roleSlug, ...dto });
}
@Delete('roles/:roleSlug')
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Удалить роль', description: 'Удаляет пользовательскую роль. Системные роли удалить нельзя.' })
@ApiParam({ name: 'roleSlug', description: 'Slug роли' })
deleteRole(@Param('roleSlug') roleSlug: string, @CurrentAdmin() admin: AdminRequestUser) {
return this.core.rbac.DeleteRole({ actorUserId: admin.id, roleSlug });
}
@Post('users/:userId/roles')
@UseGuards(SuperAdminGuard)
@ApiOperation({ summary: 'Назначить роль пользователю', description: 'Только супер-администратор может назначать роли.' })
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Назначить роль пользователю' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: AssignUserRoleDto })
assignRole(@Param('userId') userId: string, @Body() dto: AssignUserRoleDto, @CurrentAdmin() admin: AdminRequestUser) {
@@ -68,14 +99,36 @@ export class RbacController {
}
@Delete('users/:userId/roles/:roleSlug')
@UseGuards(SuperAdminGuard)
@ApiOperation({ summary: 'Снять роль с пользователя', description: 'Только супер-администратор может снимать роли.' })
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Снять роль с пользователя' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiParam({ name: 'roleSlug', description: 'Slug роли' })
removeRole(@Param('userId') userId: string, @Param('roleSlug') roleSlug: string, @CurrentAdmin() admin: AdminRequestUser) {
return this.core.rbac.RemoveUserRole({ actorUserId: admin.id, userId, roleSlug });
}
@Post('users/:userId/permissions')
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Назначить право пользователю', description: 'Прямое назначение permission без смены роли.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: AssignUserPermissionDto })
assignPermission(@Param('userId') userId: string, @Body() dto: AssignUserPermissionDto, @CurrentAdmin() admin: AdminRequestUser) {
return this.core.rbac.AssignUserPermission({ actorUserId: admin.id, userId, permissionSlug: dto.permissionSlug });
}
@Delete('users/:userId/permissions/:permissionSlug')
@UseGuards(RbacManageGuard)
@ApiOperation({ summary: 'Снять право с пользователя' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiParam({ name: 'permissionSlug', description: 'Slug права' })
removePermission(
@Param('userId') userId: string,
@Param('permissionSlug') permissionSlug: string,
@CurrentAdmin() admin: AdminRequestUser
) {
return this.core.rbac.RemoveUserPermission({ actorUserId: admin.id, userId, permissionSlug });
}
@Post('oauth-clients')
@ApiOperation({ summary: 'Создать OAuth-приложение', description: 'Создаёт OAuth2-клиент и возвращает client secret один раз.' })
@ApiBody({ type: CreateOAuthClientDto })
@@ -93,6 +146,14 @@ export class RbacController {
return this.core.rbac.UpdateOAuthClient({ actorUserId: admin.id, clientId, ...dto });
}
@Delete('oauth-clients/:clientId')
@ApiOperation({ summary: 'Удалить OAuth-приложение', description: 'Полностью удаляет OAuth2-клиент и связанные данные.' })
@ApiParam({ name: 'clientId', description: 'Client ID приложения' })
deleteOAuthClient(@Param('clientId') clientId: string, @CurrentAdmin() admin: AdminRequestUser) {
assertAdminPermission(admin, 'canManageOAuth');
return this.core.rbac.DeleteOAuthClient({ actorUserId: admin.id, clientId });
}
@Post('oauth-clients/:clientId/rotate-secret')
@ApiOperation({ summary: 'Перевыпустить client secret', description: 'Генерирует новый secret для confidential-клиента.' })
@ApiParam({ name: 'clientId', description: 'Client ID приложения' })

View File

@@ -33,6 +33,30 @@ export class AssignUserRoleDto {
roleSlug!: string;
}
export class AssignUserPermissionDto {
@ApiProperty({ description: 'Slug права', example: 'oauth.manage' })
@IsString({ message: 'Slug права должен быть строкой' })
permissionSlug!: string;
}
export class UpdateRoleDto {
@ApiPropertyOptional({ description: 'Название роли' })
@IsOptional()
@IsString({ message: 'Название роли должно быть строкой' })
name?: string;
@ApiPropertyOptional({ description: 'Описание роли' })
@IsOptional()
@IsString({ message: 'Описание должно быть строкой' })
description?: string;
@ApiPropertyOptional({ description: 'Список slug прав', type: [String] })
@IsOptional()
@IsArray({ message: 'Права должны быть массивом' })
@IsString({ each: true, message: 'Каждое право должно быть строкой' })
permissionSlugs?: string[];
}
export class CreateOAuthClientDto {
@ApiProperty({ description: 'Название приложения', example: 'Lendry Docs' })
@IsString({ message: 'Название должно быть строкой' })

View File

@@ -66,6 +66,17 @@ export class AdminGuard implements CanActivate {
}
}
@Injectable()
export class RbacManageGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<{ adminUser?: AdminRequestUser }>();
if (request.adminUser?.isSuperAdmin || request.adminUser?.canManageRoles) {
return true;
}
throw new ForbiddenException('Недостаточно прав для управления ролями и правами');
}
}
@Injectable()
export class SuperAdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {