NestJS-Authorization

jaegeunsong97·2024년 2월 22일
0

NestJS

목록 보기
35/37
post-custom-banner

🖊️IsPostMineOrAdmin Guard

patchPost의 경우 ADMIN 뿐만 아니라 본인인 경우에도 허용이 되도록 만들어야 합니다. 이것을 guard를 사용해서 만들어보겠습니다.

  • posts/guard/is-post-mine-or-admin.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { RolesEnum } from "src/users/const/roles.const";
import { PostsService } from "../posts.service";

@Injectable()
export class IsPostMineOrAdminGuard implements CanActivate {
     
    constructor(
    	private readonly postService: PostsService,
    ) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const req = context.switchToHttp().getRequest() as Request & {user: UsersModel}; // 인터섹션(user는 존재한다.)
        const {user} = req;
        if (!user) throw new UnauthorizedException(`사용자 정보를 가져올 수 없습니다. `);

        /**
         * Admin일 경우 그냥 패스
         */
        if (user.role === RolesEnum.ADMIN) return true;

        const postId = req.params.postId;
        if (!postId) throw new BadRequestException(`Post ID가 파라미터로 제공 돼야합니다. `)

        return this.postService.isPostMine(
            user.id,
            parseInt(postId)
        );
     }
}

post 서비스에서 해당 post가 나의 것인지 확인하는 코드를 작성하겠습니다.

  • posts.service.ts
async isPostMine(userId: number, postId: number) {
    return this.postsRepository.exists({
        where: {
            id: postId,
            author: {
              	id: userId,
            }
        },
      	relations: {
            author: true,
        }
    })
}

이제 적용을 하겠습니다.

  • posts.controller.ts
@Patch(':id')
@UseGuards(IsPostMineOrAdmin)
patchPost(
    @Param('id', ParseIntPipe) id: number, 
    @Body() body: UpdatePostDto,
) {
    return this.postsService.updatePost(id, body);
}

{
    "message": "Post ID가 파라미터로 제공 돼야합니다. ",
    "error": "Bad Request",
    "statusCode": 400
}

이유는 컨트롤러에서 값을 받을 때 id로 받기 때문입니다. 따라서 바꿔줍니다.

@Patch(':postId')
@UseGuards(IsPostMineOrAdmin)
patchPost(
    @Param('postId', ParseIntPipe) id: number, 
    @Body() body: UpdatePostDto,
) {
    return this.postsService.updatePost(id, body);
}

{
    "id": 91,
    "updatedAt": "2024-02-21T22:14:39.664Z",
    "createdAt": "2024-01-28T02:12:54.444Z",
    "title": "Authorization Check",
    "content": "임의로 생성된 포수트 내용 82",
    "likeCount": 0,
    "commentCount": 0
}

만약 다른 사용자로 PATCH 요청을 보내면 forbidden 에러가 발생합니다. 이 에러 메세지를 작성하겠습니다.

  • posts/guard/is-post-mine-or-admin.guard.ts
@Injectable()
export class IsPostMineOrAdminGuard implements CanActivate {
     
    constructor(
    	private readonly postService: PostsService,
    ) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const req = context.switchToHttp().getRequest() as Request & {user: UsersModel};
        const {user} = req;
        if (!user) throw new UnauthorizedException(`사용자 정보를 가져올 수 없습니다. `);

        if (user.role === RolesEnum.ADMIN) return true;

        const postId = req.params.postId;
        if (!postId) throw new BadRequestException(`Post ID가 파라미터로 제공 돼야합니다. `)

      	// 추가
        const isOk = await this.postService.isPostMine(
            user.id,
            parseInt(postId)
        );
        if (!isOk) throw new ForbiddenException(`권한이 없습니다. `);
        return true;
    }
}

다른 사용자로 로그인 후에 다시 요청해보겠습니다.

{
    "message": "권한이 없습니다. ",
    "error": "Forbidden",
    "statusCode": 403
}

관리자 계정으로 로그인 후, PATCH요청을 해도 잘 실행되는 것을 알 수 있습니다.


🖊️IsCommentMineOrAdmin Guard 생성 및 적용

이번에는 comment 또한 ADMIN과 실제 사용자만 가능하도록 만들겠습니다.

  • comments/guard/is-comment-mine-or-admin.guard.ts
@Injectable()
export class IsCommentMineOrAdminGuard implements CanActivate {
     
    constructor(
    	private readonly commentService: CommentsService,
    ) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const req = context.switchToHttp().getRequest() as Request & {user: UsersModel};
        const {user} = req;
        if (!user) throw new UnauthorizedException(`사용자 정보를 가져올 수 없습니다. `);

        if (user.role === RolesEnum.ADMIN) return true;

        const commentId = req.params.commentId;
        if (!commentId) throw new BadRequestException(`Comment ID가 파라미터로 제공 돼야합니다. `)

        const isOk = await this.commentService.isCommentMine(
            user.id,
            parseInt(commentId)
        );
        if (!isOk) throw new ForbiddenException(`권한이 없습니다. `);
        return true;
    }
}
  • comments.service.ts
async isCommentMine(userId: number, commentId: number) {
    return this.commentsRepository.exists({
        where: {
            id: commentId,
            author: {
              	id: userId,
            }
        },
        relations: {
          	author: true,
        }
    })
}
  • comment.controller.ts
@Patch(':commentId')
@UseGuards(IsCommentMineOrAdminGuard)
async patchComment(
    @Param('commentId', ParseIntPipe) commentId: number,
    @Body() body: UpdateCommentsDto,
) {
    return this.commentsService.updateComment(
        body,
        commentId
    )
}

@Delete(':commentId')
@UseGuards(IsCommentMineOrAdminGuard)
async deleteComment(
  	@Param('commentId', ParseIntPipe) commentId: number,
) {
  	return this.commentsService.deleteComment(commentId);
}

포스트맨으로 테스트 진행하기

profile
블로그 이전 : https://medium.com/@jaegeunsong97
post-custom-banner

0개의 댓글