Свои декораторы
Декораторы @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().