Encodera

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

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

Оглавление

Свои декораторы

Декораторы @Body() и @Param() достают данные из запроса, а @Roles() из прошлого урока оставляет пометку на методе. Таких помощников можно писать самостоятельно. Чаще всего нужны два: декоратор параметра, который отдаёт текущего пользователя, и декоратор, который объединяет несколько других в один.

Декоратор параметра

Сейчас, чтобы узнать пользователя, методу пришлось бы принимать весь объект запроса и читать request.user. Функция createParamDecorator() делает это короче:

// src/auth/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
 
export const CurrentUser = createParamDecorator(
  (field: string | undefined, context: ExecutionContext) => {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return field ? user?.[field] : user;
  },
);

Функция получает аргумент декоратора (field) и контекст запроса и возвращает то, что попадёт в параметр метода. Если поле не указано, возвращается весь пользователь.

Использование

Добавьте в AuthController маршрут профиля:

// src/auth/auth.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { CurrentUser } from './current-user.decorator';
 
@Controller('auth')
export class AuthController {
  // конструктор и метод login() из прошлого урока
 
  @Get('profile')
  @UseGuards(AuthGuard)
  profile(@CurrentUser() user: { sub: number; email: string; role: string }) {
    return user;
  }
}

Запрос GET /auth/profile с токеном вернёт содержимое токена: sub, email, role, а также служебные поля iat и exp — время выдачи и окончания действия.

С аргументом декоратор отдаёт одно поле. Так статья узнаёт номер автора:

// src/posts/posts.controller.ts
@Post()
@UseGuards(AuthGuard)
create(@Body() dto: CreatePostDto, @CurrentUser('sub') authorId: number) {
  return this.postsService.create(dto, authorId);
}

Для этого в сущность Post добавляют связь @ManyToOne(() => User) со свойством author — так же, как связь комментария со статьёй.

Объединение декораторов

Над каждым методом для администратора повторяются одни и те же строки: @Roles('admin'), @UseGuards(AuthGuard, RolesGuard). Функция applyDecorators() собирает их в один декоратор:

// src/auth/auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
 
export function Auth(...roles: string[]) {
  return applyDecorators(Roles(...roles), UseGuards(AuthGuard, RolesGuard));
}
// src/posts/posts.controller.ts
import { Auth } from '../auth/auth.decorator';
 
@Delete(':id')
@HttpCode(204)
@Auth('admin')
remove(@Param('id', ParseIntPipe) id: number) {
  return this.postsService.remove(id);
}

@Auth() без аргументов пустит любого вошедшего пользователя, @Auth('admin') — только администратора. Порядок гардов теперь задан в одном месте, и ошибиться в нём нельзя.

Частая ошибка

Декоратор @CurrentUser() не проверяет вход — он только читает request.user. Если над методом нет AuthGuard, пользователь в запросе не появится, и в параметр придёт undefined. Используйте @CurrentUser() только вместе с гардом входа или с декоратором @Auth().

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

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