Роли и права доступа
Вход отвечает на вопрос «кто вы», а авторизация — «что вам можно». В блоге статьи пишут все вошедшие пользователи, а удалять чужие статьи может только администратор. Роль пользователя уже хранится в поле role и попадает в токен, осталось её проверять.
Декоратор Roles
Метод контроллера нужно пометить списком разрешённых ролей. Для этого создайте свой декоратор на основе SetMetadata() — функции, которая прикрепляет к методу произвольные данные (метаданные):
// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);Теперь запись @Roles('admin') над методом сохранит у него массив ['admin'] под ключом roles. Сам декоратор ничего не проверяет — он только оставляет пометку.
Гард ролей
Гард читает пометку через сервис Reflector и сравнивает её с ролью пользователя:
// src/auth/roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!roles || roles.length === 0) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return roles.includes(user?.role);
}
}getAllAndOverride() ищет пометку сначала у метода, потом у класса контроллера. Если пометки нет или список ролей пуст, маршрут открыт всем вошедшим. Объект user положил в запрос AuthGuard из прошлого урока.
Использование
// src/posts/posts.controller.ts
import { Controller, Delete, HttpCode, Param, ParseIntPipe, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { Roles } from '../auth/roles.decorator';
import { RolesGuard } from '../auth/roles.guard';
import { PostsService } from './posts.service';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Delete(':id')
@HttpCode(204)
@Roles('admin')
@UseGuards(AuthGuard, RolesGuard)
remove(@Param('id', ParseIntPipe) id: number) {
return this.postsService.remove(id);
}
}Гарды выполняются в порядке перечисления: сначала AuthGuard проверяет токен и кладёт пользователя в запрос, затем RolesGuard проверяет роль. Обычный пользователь получит на DELETE /posts/1 ответ 403 Forbidden resource.
Назначение администратора
Маршрута для смены роли пока нет, поэтому первого администратора назначают прямо в базе:
UPDATE user SET role = 'admin' WHERE email = 'anna@example.com';После этого Анне нужно войти заново: роль записана в токен, и старый токен содержит прежнее значение.
Права на свои записи
Роли не решают задачу «автор может менять только свои статьи» — это зависит от конкретной статьи. Такую проверку пишут в сервисе: загружают статью и сравнивают номер её автора с user.sub, а при несовпадении бросают ForbiddenException.
Частая ошибка
Если в @UseGuards() поставить RolesGuard раньше AuthGuard, в момент проверки роли пользователя в запросе ещё нет, и все запросы получат 403 — даже от администратора. Гард входа всегда должен идти первым.