팀프로젝트 1일차 기술 회고

·2022년 5월 9일
0

팀 프로젝트

목록 보기
3/34

이건 그냥 내가 쓰고 싶어서 쓰는 페이지다.

이걸 회고라고 불러야할지는 모르겠지만 마땅한 용어가 생각이 안나서(...)
매일매일 작업한 내용의 코드를 리뷰하는 그런 장이 될 것이다!


오늘 작업한 분량

로그인 자체 구현은 주말에 미리 다 해놔서 로그아웃 디테일이랑 핸드폰 인증을 구현해놨다.

나도 pass 인증 쓰고 싶다!!!!

이 api를 쓰려면 서류도 작성하고 이것저것 다 해야해서 쓰질 못했다 ㅠ_ㅠ

그래서 그냥 과거 수업에 했던 NHN Cloud의 SMS 시스템을 사용하기로 했다.

요기서 사용할 수 있다! => https://www.toast.com/kr

구현 자체는 복잡하지 않고, 그냥 어떤식으로 인증을 해야하나 고민을 하게 되었다.
예전 수업에서 들었을 때는 mongoDB를 사용했었는데, 현재는 Mysql을 메인 DB로 사용하고 있어서
두개의 DB를 한번에 사용하는 것은 과욕이라고 판단(...)해서 그럼 비슷한 모양의 Nosql Redis를 쓰면 되겠다는 생각을 했다.

토큰을 발송해주는 코드

async sendTokenToPhone({ phone }) {
    const token = String(Math.floor(Math.random() * 10 ** 6)).padStart(6, '0');
    const tokenData = await this.cacheManager.get(phone);
    await this.cacheManager.set(phone, token, { ttl: 300 });
    await this.httpService
      .post(
        `https://api-sms.cloud.toast.com/sms/v3.0/appKeys/${process.env.SMS_APP_KEY}/sender/sms`,
        {
          body: `안녕하세요 인증번호는 [${token}]입니다.`,
          sendNo: process.env.SMS_SENDER,
          recipientList: [
            {
              internationalRecipientNo: phone,
            },
          ],
        },
        {
          headers: {
            'Content-Type': 'application/json;charset=UTF-8',
            'X-Secret-Key': process.env.SMS_X_SECRET_KEY,
          },
        },
      )
      .toPromise();
    if (tokenData === null) {
      return `인증번호가 발송되었습니다.`;
    } else {
      return `인증번호가 변경되었습니다.`;
    }
  }

그래서 이런식의 코드가 나오게 되었다.
조금 변경점이 있어도 괜찮을 것 같은데, 아직은 고민을 하고 있는 상태랄까...

api를 요청하면 해당하는 6자리의 숫자로 이루어진 난수값을 value 핸드폰 번호를 key에 담고 300초 저장시간으로 뒀다.

300초로 둔 이유는 많은 서비스에서 본인 인증 서비스가 3:00으로 되어있는 경우가 많아서 그것을 참고삼아서 300초로 정했고
해당하는 token을 유저한테 발송했을 때

최초의 경우에는 해당 번호로 Redis에서 검색했을 경우 null이 나오게 되니 인증번호가 발송되었습니다. 를 리턴하고
300초 내로 여러번 반복할 경우 값이 존재하기 때문에 새로운 값으로 덮어씌워주고 인증번호가 변경되었습니다. 를 리턴하기로 했다.

토큰을 검증하는 코드

async checkToken({ phone, token }) {
    const tokenData = await this.cacheManager.get(phone);
    if (tokenData === token) {
      return true;
    } else {
      return false;
    }
  }

여기는 뭐 길게 필요가 없는 것 같아서 슥삭(?) 해버린 것 같다.

핸드폰 번호로 Redis에서 찾아왔을 때 입력한 token 값이 동일하다면 true 아니면 false를 리턴해주는데
이 값은 프론트단에서 어떻게 쓰냐에 따라서 Boolean 값이 아니라 다른 형태로 변경을 해줘야겠다는 생각도 하고 있다.

로그아웃 디테일

사실 로그아웃은 저번 과제에 있었는데 그 조건에 내가 안맞게 했더라(...) 그래서 삽질을 조금 했다...

토큰을 발급할 때 시간을 설정해주는 옵션이 존재하는데 그것이 일반적인 시간으로 입력되는 것이 아니라서
그것을 이용하여 남은 시간을 체크하는 것은 조금의 품이 들 수 밖에 없는 것 같았다.

더 짧게 할 수 있다면 좋겠지만, 과연 어떻게 해야할지는 고민을 좀 해봐야할 문제일 것 같다.

로그아웃 코드

@Mutation(() => String)
  async logout(
    //
    @Context() context: any,
  ) {
    const now = new Date();
    const access = context.req.headers.authorization.replace('Bearer ', '');
    const access_decoded = this.jwtService.decode(access);
    const access_time = new Date(access_decoded['exp'] * 1000);
    const access_end = Math.floor(
      (access_time.getTime() - now.getTime()) / 1000,
    );
    const refresh = context.req.headers.cookie.replace('refreshToken=', '');
    const refresh_decoded = this.jwtService.decode(refresh);
    const refresh_time = new Date(refresh_decoded['exp'] * 1000);
    const refresh_end = Math.floor(
      (refresh_time.getTime() - now.getTime()) / 1000,
    );
    try {
      jwt.verify(access, this.config.get('ACCESS'));
      jwt.verify(refresh, this.config.get('REFRESH'));
      await this.cacheManager.set(access, 'accessToken', { ttl: access_end });
      await this.cacheManager.set(refresh, 'refreshToken', {
        ttl: refresh_end,
      });
      return '로그아웃에 성공했습니다';
    } catch {
      throw new UnauthorizedException();
    }
  }

쓸때없이 길다하면 할 말이 없는데 ㅠ_ㅠ 저렇게 안하고는 시간을 계산해내려면 한줄로 엄청 길어질 것 같아서 그냥 딱딱 잘라서 분리해놨다.
어....? 뭔가 try catch를 쓰는데 위에서 굳이 계산을 해야할까 라는 생각이 들어서 이것도 조금 수정이 필요할 것 같다...
왜냐하면 jwt.verify에서도 exp가 조회가 되다보니 자고 일어나서 이건 조금 리팩토링을 해야할 것 같다.

그래봐야 코드가 크게 줄어들 것 같진 않은데 혹시 모르니 한번 줄여는 봐야겠다.

어떤 형식을 쓰고 있냐면 해당하는 액세스 토큰과 리프레시 토큰을 담고, 시간 설정을 남은 시간으로 해놔서
UseGraud를 단에서 req에 액세스 토큰으로 레디스에서 긁어왔을 때 뭔가 존재하면 로그아웃됐다! 라는 식으로 구성을 해놨다.

코드 자체는 이렇게 구성을 해놨다. 쿼리빌더로 가져온건 토큰으로 활용할게 많아서 추가를 해놓은 상태다.

 async validate(req, payload) {
    const access = req.headers.authorization.replace('Bearer ', '');
    const check = await this.cacheManager.get(access);
    if (check) throw new UnauthorizedException();
    const user = await this.userRepository
      .createQueryBuilder()
      .where({ user_email: payload.user_email })
      .getOne();
    return {
      user_email: payload.user_email,
      user_id: user.user_id,
    };
  }

오늘은 이정도 작업했고 내일은 뭐를 해야하나 . . . . 생각보다 작업을 하고 싶어도 못하는 상황이 된 것 같아서 붕 떠버린 느낌이다.
뭐하지...?

profile
물류 서비스 Backend Software Developer

0개의 댓글