심화 팀 프로젝트
개인 프로젝트가 끝나자마자 바로 팀 프로젝트가 시작되었다.
이번 프로젝트는 칸반 보드를 만들 예정이다.
협업을 위한 노션 페이지와, DISCORD를 생성하고 GITHUB
레포지도 새로 생성하고 Nestjs
파일과 typeORM
을 연결하고 초기 세팅을 한 채로 만들었다.
와이어프레임
깃 허브 규칙을 정했다.
ERD
프로젝트 초기 세팅을 마치고 내가 맡은 부분인 CARD를 구현하고 있는데,
카드 뿐만 아니라 보드와 컬럼도 에서도 해당 유저가 해당 보드에 "초대된" 유저인지 확인할 수 있는 방법이 필요했다.
그래서 처음에는 각 서비스에서 직접 확인하는 로직을 짜고 있었는데 짜고 보니까 코드가 너무 중복되고 더러워 지는 것 같아서 하나로 깔끔하게 묶어줘서 필요한 곳에서 그때 그때 불러주는 방법은 없을까 하고 생각해보니까 우리가 그동안 JWT할 때 만들어서 썼던 Guard
가 있었다.
그래서 가드를 만들고자 했었는데 먼저 nestjs 공식 문서에서 찾아보았다.
NestJS에서는 가드를 만들기 위해서는 @nestjs/common
모듈에서 제공하는 CanActivate
라는 인터페이스를 구현하는 클래스를 생성해야 한다. 그리고 canActivate()
메서드를 안에 가드의 로직을 작성할 수 잇다.
예를 들면
import { Injectable, CanActivate } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate() {
console.log('인증이 성공하였습니다.');
return true;
}
}
NestJS는 canActivate()
함수가 true
또는 promise<true>
를 반활했을 때만 해당 요청을 컨트롤러로 전달한다
만약 false
가 전달되면 컨트롤러로 넘어가는 것을 차단한다.
// src/auth/guard/board-invitation.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { BoardInvitation } from 'src/board-invitations/entities/board-invitation.entity';
import { Board } from 'src/boards/entities/board.entity';
import { Repository } from 'typeorm';
@Injectable()
export class BoardInvitationGuard implements CanActivate {
constructor(
@InjectRepository(BoardInvitation)
private readonly boardInvitationRepository: Repository<BoardInvitation>,
@InjectRepository(Board)
private readonly boardRepository: Repository<Board>,
) {}
// canActivate 메소드는 요청이 허용되는지 여부를 결정하는 역할을 합니다.
async canActivate(context: ExecutionContext): Promise<boolean> {
// 현재 HTTP 요청을 가져옵니다.
const request = context.switchToHttp().getRequest();
// 요청한 사용자의 ID와 보드 ID를 추출합니다.
const userId = request.user.id;
const { boardId } = request.params;
// 요청된 보드가 존재하는지 확인합니다.
const existingBoard = await this.boardRepository.findOne({
where: { id: boardId },
});
// 보드가 없다면 NotFoundException을 발생시킵니다.
if (!existingBoard) {
throw new NotFoundException('보드를 찾을 수 없습니다.');
}
// 현재 사용자가 해당 보드에 초대된 사용자인지 확인합니다.
const existedUser = await this.boardInvitationRepository.findOne({
where: {
user: { id: userId },
status: 'invited',
board: { id: boardId },
},
});
// 초대된 사용자가 아니라면 UnauthorizedException을 발생시킵니다.
if (!existedUser) {
throw new UnauthorizedException('조작할 권한이 없습니다.');
}
// 모든 검증이 통과하면 허용합니다.
return true;
}
}
해당 가드를 만들고 난 뒤 필요한 컨트롤러에서 가드를 사용하면 된다. 하지만 여기서 중요한 점은 꼭 jwt가드를 통과하고 나서 할 수 있다 왜냐하면 userId를 받아야하기 때문에 jwt가드를 먼저 통과해야 한다.
또한 해당 가드는
BoardInvitationRepository
와BoardRepository
를 사용하기 때문에 해당 사용하는 해당 모듈에서는 저 두개의 엔티티를 불러와야 한다.
해당 모듈에서 불러온 모습.