Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Роли и права доступа

Вход отвечает на вопрос «кто вы», а авторизация — «что вам можно». В блоге статьи пишут все вошедшие пользователи, а удалять чужие статьи может только администратор. Роль пользователя уже хранится в поле 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 — даже от администратора. Гард входа всегда должен идти первым.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.